V1_1.java [src/java/m/rse/wepot] Revision:   Date:
package m.rse.wepot;

import csip.Config;
import csip.ModelDataService;
import csip.ServiceException;
import csip.SessionLogger;
import csip.annotations.Polling;
import csip.annotations.Resource;
import static csip.annotations.ResourceType.FILE;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.Path;
import m.rse.cfactor.utils.Const;
import static m.rse.cfactor.utils.Const.RSE_DEPLOYMENT_LINUX;
import m.rse.cfactor.utils.DBResources;
import static m.rse.cfactor.utils.DBResources.CFACTOR_ID;
import static m.rse.cfactor.utils.DBResources.LEGACY_CFACTOR_ID;
import m.rse.cfactor.utils.PGTools;
import oms3.annotations.Description;
import oms3.annotations.Name;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

/**
 *
 * @author Shaun Case
 * @author wl
 */
@Name("wepot")
@Description("This service computes water and wind erodibility potentials for an area of analysis (AoA).  The service clips SSURGO soil mapunits with AoA geometry, determines the dominant soil component in the AoA, gets parameters from the SSURGO component table, including a climate factor from the C Factor layer, and computes the following equations:  Wind Erosion Potential = C*I/T ;  Water Erosion Potential = K*(LS)/T")
@Path("m/wepot/1.1")
@Polling(first = 5000, next = 2000)
@Resource(file = "/data/us_cvalues_topo2ras_masked.tif", type = FILE, id = "tif_file")
@Resource(file = "/data/us_cvalues_topo2ras_masked.tfw", type = FILE, id = "tfw_file")
@Resource(file = "/data/us_cvalues_topo2ras_masked.tif.aux.xml", type = FILE, id = "tifxml_file")
@Resource(from = DBResources.class)

public class V1_1 extends ModelDataService {

    static final int JSON_LATITUDE = 1;
    static final int JSON_LONGITUDE = 0;
    static final String RSE_AOAID = "AoAId";
    static final String AOA_GEOMETRY = "aoa_geometry";

    private String AoAId;
    private JSONObject aoaGeometry;
    private PGTools.PolygonLatLon polygonData;

    //Result variables
    private double aoa_cfactor;
    private String aoa_dom_comp;
    private String aoa_dom_compname;
    private String aoa_dom_wind_comp;
    private String aoa_dom_wind_compname;
    private double aoa_ifactor;
    private double aoa_tfactor;
    private double aoa_wind_tfactor;
    private double aoa_lsfactor;
    private double aoa_kfactor;
    private double aoa_wind_ep;
    private double aoa_water_ep;

    private boolean isPalouse;

    double cfactorraster = -1;

    @Override
    protected void preProcess() throws ServiceException {
        this.AoAId = "Error";
        this.polygonData = null;
        this.aoaGeometry = null;

        this.aoa_cfactor = Const.UNKNOWN_CFACTOR;
        this.aoa_dom_comp = "";
        this.aoa_dom_compname = "";
        this.aoa_ifactor = Const.UNKNOWN_CFACTOR;
        this.aoa_tfactor = Const.UNKNOWN_CFACTOR;
        this.aoa_dom_wind_comp = "";
        this.aoa_dom_wind_compname = "";
        this.aoa_wind_tfactor = Const.UNKNOWN_CFACTOR;
        this.aoa_lsfactor = Const.UNKNOWN_CFACTOR;
        this.aoa_kfactor = Const.UNKNOWN_CFACTOR;
        this.aoa_wind_ep = Const.UNKNOWN_CFACTOR;
        this.aoa_water_ep = Const.UNKNOWN_CFACTOR;
        this.isPalouse = false;

        try {
            //These functions will throw a usuable user message if they cannot find these values.
            //  Just catch it and return it to the user.
            this.AoAId = getStringParam(V1_1.RSE_AOAID);
            this.aoaGeometry = getJSONParam(V1_1.AOA_GEOMETRY);
        } catch (ServiceException ex) {
            throw new ServiceException("Cannot process request JSON:  " + ex.getMessage(), ex);
        }
    }

    @Override
    protected void doProcess() throws ServiceException, SQLException {
        ArrayList<V1_1.ssurgoAttributes> intersectionAttributes;
        try {
            JSONArray features = this.aoaGeometry.optJSONArray("features");
            if (this.buildPolygon(features)) {

                this.polygonData.makeESRIRotation(PGTools.PolygonLatLon.ESRIContourType.outer);

                //Got a valid Polygon now.  
                //HINT:  You can get the WKT from the PolygonLatLon object to build an SQL query via the "toWKT" funciton, 
                //       and then encasing that WKT in the typical "geometry::STPolyFromText(<KWT goes here>,4326)", etc.  
                LOG.info("THE POLYGON AS WE KNOW IT IS=" + this.polygonData.toWKT());
                String cfactor_polygon = "geometry::STPolyFromText(" + this.polygonData.toWKT() + ",4326)";
                LOG.info("getting centroid for polygon now");
                PGTools.Centroid centroid = this.getCentroid(cfactor_polygon, LOG);
                LOG.info("About to get C Factor Raster value for lat=" + centroid.lat + " long=" + centroid.lon + " ");

                //Need to discover if this centroid is not in CONUS before calling getCFactor as it only has a raster map of CONUS...
                if (isCONUS(Double.parseDouble(centroid.lat), Double.parseDouble(centroid.lon))) {
                    this.aoa_cfactor = PGTools.getCFactorRaster(centroid.lat, centroid.lon, Config.getString("gdal.deployment", RSE_DEPLOYMENT_LINUX).equals(RSE_DEPLOYMENT_LINUX) ? (new File(getResourceFile("tif_file").getParentFile().getAbsolutePath())) : getWorkspaceDir(), LOG) / 100.0;
                } else {
                    this.aoa_cfactor = getAKCFactor(Double.parseDouble(centroid.lat), Double.parseDouble(centroid.lon)) / 100.0;
                }

                //  The result of this call will be "null" if no intersection was found.
                intersectionAttributes = this.ssurgoIntersect(this.polygonData.toWKT());
                this.isPalouse = this.isPalouseArea(this.polygonData.toWKT());

                if (null != intersectionAttributes) {
                    if (this.windCriticalComponentLookup(intersectionAttributes)) {
                        this.aoa_wind_ep = this.aoa_cfactor * this.aoa_ifactor / this.aoa_wind_tfactor;

                        if (this.waterCriticalComponentLookup(intersectionAttributes)) {
                            this.aoa_water_ep = this.aoa_kfactor * this.aoa_lsfactor / this.aoa_tfactor;
                        } else {
                            throw new ServiceException("Could not calculate the water erodibility potenial.  Missing component information. ");
                        }
                    } else {
                        throw new ServiceException("Could not calculate the wind erodibility potenial.  Missing component information. ");
                    }
                }
            }
        } catch (IOException ioe) {
            throw new ServiceException("IO Exception getting cFactor value from raster geotiff layer:  " + ioe.getMessage(), ioe);
        } catch (JSONException ex) {
            throw new ServiceException("Error processing request geometry: " + ex.getMessage(), ex);
        }
    }

    @Override
    protected void postProcess() throws Exception {
        putResult("aoa_id", this.AoAId);
        putResult("aoa_dom_water_comp", this.aoa_dom_comp);
        putResult("aoa_dom_water_compname", this.aoa_dom_compname);
        putResult("aoa_dom_wind_comp", this.aoa_dom_wind_comp);
        putResult("aoa_dom_wind_compname", this.aoa_dom_wind_compname);
        putResult("aoa_cfactor", this.aoa_cfactor);
        putResult("aoa_ifactor", this.aoa_ifactor);
        putResult("aoa_tfactor", this.aoa_tfactor);
        putResult("aoa_wind_tfactor", this.aoa_wind_tfactor);
        putResult("aoa_lsfactor", this.aoa_lsfactor);
        putResult("aoa_kfactor", this.aoa_kfactor);
        putResult("aoa_wind_ep", this.aoa_wind_ep);
        putResult("aoa_water_ep", this.aoa_water_ep);
    }

    //Private functions
    private Boolean buildPolygon(JSONArray features) throws JSONException, ServiceException {
        Boolean ret_val = false;

        if (null != features) {
            if (features.length() > 0) {
                JSONObject feature = features.getJSONObject(0).optJSONObject("geometry");
                if (this.isGeometryPolygonType(feature)) {
                    JSONArray polygon = feature.optJSONArray("coordinates");
                    if (null != polygon) {
                        if (polygon.length() > 0) {
                            this.polygonData = readPolygonCoordinates(polygon.getJSONArray(0));
                            if (null != this.polygonData) {
                                if (!this.polygonData.isValid()) {
                                    throw new ServiceException("Invalid latitude and/or longitude data contained in this polygon.  Cannot proceed with processing of this request. ");
                                } else {
                                    ret_val = true;
                                }
                            }//No else needed here, the error message will be built in readPolygonCoordinates()
                        } else {
                            throw new ServiceException("No coordinates found associated with the polygon specified in feature collection number:  1 . ");
                        }
                    } else {
                        throw new ServiceException("No coordinates found associated with the polygon specified in feature collection number:  1 . ");
                    }
                }//No else needed here, the error message will be built in isGeometryPolygonType()
            } else {
                throw new ServiceException("No geometry found associated with this feature. ");
            }
        } else {
            throw new ServiceException("Cannot process request JSON, missing features. ");
        }

        return ret_val;
    }

    public double getAKCFactor(double lat, double lon) throws SQLException, ServiceException {
        double ret_val = 0.0;
        String query = "select TOP 1 name, (geometry::STGeomFromText('POINT(" + lon + " " + lat + ")',4326).STDistance(geom)) as distance  FROM r2gis.alaska_points order by distance;";

        try (Connection conn = getResourceJDBC(CFACTOR_ID);
                Statement statement = conn.createStatement();) {

            ResultSet results = statement.executeQuery(query);

            if (results.next()) {
                ret_val = results.getDouble("name");
            } else {
                throw new ServiceException("Could not find an Alaska C_Factor for that input geography. No results returned from the Alaska Points query.");
            }
        }
        return ret_val;
    }

    private boolean isCONUS(double lat, double lon) throws SQLException, ServiceException {
        boolean ret_val = false;

        String query = "SELECT top 1 areasymbol, "
                + "CASE WHEN SUBSTRING(areasymbol, 1, 2) in ('AL','AZ','AR','CA','CO','CT','DC','DE','FL','GA','HI','ID','IL','IN','IA','KS','KY','LA','ME','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY')THEN 1 ELSE 0 END  as CONUS "
                + " from ssurgo.soilmu_a "
                + "WITH (index(geom_sidx)) "
                + "WHERE the_geom.STIntersects(geometry::STGeomFromText('POINT("
                + lon + " " + lat + ")', 0)) = 1;";

        try (Connection conn = getResourceJDBC(CFACTOR_ID);
                Statement statement = conn.createStatement();) {

            ResultSet results = statement.executeQuery(query);

            if (results.next()) {
                ret_val = results.getBoolean("CONUS");
            } else {
                throw new ServiceException("Cannot determine if the location provided is within CONUS.  isCONUS query returned no results.");
            }
        }

        return ret_val;
    }

    private Boolean isGeometryPolygonType(JSONObject geometry) throws ServiceException {
        Boolean ret_val = false;

        if (null != geometry) {
            String geometryType;

            geometryType = geometry.optString("type");
            if (!geometryType.isEmpty()) {
                if (geometryType.toLowerCase().equals("polygon")) {
                    ret_val = true;
                } else {
                    throw new ServiceException("No valid geometry type found in the feature collection number:  1 .  Looking for 'Polygon'. ");
                }
            } else {
                throw new ServiceException("No geometry type specified in the feature collection number:  1 . ");
            }
        } else {
            throw new ServiceException("No geometry found in the feature collection number:  1 . ");
        }

        return ret_val;
    }

    private PGTools.PolygonLatLon readPolygonCoordinates(JSONArray shape) throws JSONException, ServiceException {
        PGTools.PolygonLatLon tPolygon = new PGTools.PolygonLatLon();

        if (null != shape) {
            for (int k = 0; k < shape.length(); k++) {
                JSONArray jPoint = shape.getJSONArray(k);
                tPolygon.add(jPoint.getDouble(V1_1.JSON_LATITUDE), jPoint.getDouble(V1_1.JSON_LONGITUDE));
            }
        } else {
            tPolygon = null;
            throw new ServiceException("Cannot find the latitude and longitude values for this polygon. ");
        }

        return tPolygon;
    }

    // If the WKTPolygon of interest intersects any polygons in the Palouse, MLRA-9, region, this funciton will return true.
    private boolean isPalouseArea(String WKTPolygon) throws ServiceException {
        boolean ret_val = false;

        if (null != WKTPolygon) {
            String query;

            query = "SELECT id"
                    + " FROM ssurgo.mlra_9 "
                    + " WHERE geometry::STGeomFromText(component_boundary.STAsText(),4326).STIntersects(geometry::STGeomFromText(" + WKTPolygon + ",4326))=1"
                    + " AND geometry::STGeomFromText(" + WKTPolygon + ",4326).STIsValid()=1;";

            LOG.log(Level.INFO, "ssurgo Palouse query={0}", query);

            try (Connection conn = getResourceJDBC(CFACTOR_ID);
                    Statement statement = conn.createStatement();) {
                ResultSet results = statement.executeQuery(query);

                LOG.info("   past the query execute");

                if (null != results) {
                    if (results.next()) {
                        ret_val = true;
                    }
                }

            } catch (SQLException ex) {
                throw new ServiceException("Cannot continue processing this request in the find Palouse region procedure:  " + ex.getMessage(), ex);
            }
        }

        return ret_val;
    }

    // Returns the intersected mukeys
    private ArrayList<V1_1.ssurgoAttributes> ssurgoIntersect(String WKTPolygon) throws ServiceException {
        HashMap<String, V1_1.ssurgoAttributes> mukeyAttributes;
        ArrayList<ssurgoAttributes> attributeList;

        ArrayList<V1_1.ssurgoAttributes> ret_val = null;

        if (null != WKTPolygon) {
            String query;
            mukeyAttributes = new HashMap<>();
            attributeList = new ArrayList<>();

            query
                    = "SELECT areasymbol, musym, mukey, geography::STGeomFromText(intersectPoly.STAsText(), 4326).MakeValid().STArea() / 4046.86 as sizeIntersectionAcres, "
                    + " geography::STGeomFromText(" + WKTPolygon + ", 4326).MakeValid().STArea() / 4046.86 as aoaArea"
                    + " FROM"
                    + " (SELECT m.areasymbol, m.musym, m.mukey, "
                    + " m.the_geom.STIntersection(geometry:: STGeomFromText (" + WKTPolygon + ",0)).MakeValid() as intersectPoly"
                    + " FROM ssurgo.soilmu_a as m "
                    + " WITH (index(geom_sidx))"
                    + " WHERE m.the_geom.STIntersects(geometry:: STGeomFromText (" + WKTPolygon + ",0)) = 1"
                    + " AND m.the_geom.STIsValid()=1"
                    + " AND (geometry:: STGeomFromText (" + WKTPolygon + ",0).STIsValid())=1)"
                    + " as a;";

            LOG.info("ssurgo query=" + query);
            try (Connection conn = getResourceJDBC(CFACTOR_ID);
                    Statement statement = conn.createStatement();) {
                ResultSet results = statement.executeQuery(query);

                LOG.info("   past the query execute");

                if (null != results) {
                    while (results.next()) {
                        V1_1.ssurgoAttributes tAttributes;
                        String tKey = results.getString("areasymbol") + ":" + results.getString("mukey");

                        tAttributes = mukeyAttributes.get(tKey);

                        //Merge, i.e. sum, areas of duplicate AoA:mukey combinations
                        if (null != tAttributes) {
                            tAttributes.gid_area += results.getDouble("sizeIntersectionAcres");
                        } else {
                            tAttributes = new V1_1.ssurgoAttributes();
                            tAttributes.gid = results.getString("musym");
                            tAttributes.AoA_Id = results.getString("areasymbol");
                            tAttributes.mukey = results.getString("mukey");
                            tAttributes.gid_area = results.getDouble("sizeIntersectionAcres");
                            tAttributes.aoa_area = results.getDouble("aoaArea");

                            mukeyAttributes.put(tKey, tAttributes);
                            attributeList.add(tAttributes);
                        }
                    }

                    ret_val = attributeList;
                } else {
                    throw new ServiceException("No results from the intersect query for this geometry. No map units found.");
                }
            } catch (SQLException ex) {
                throw new ServiceException("Cannot continue processing this request in the intersect procedure:  " + ex.getMessage(), ex);
            }
        }

        return ret_val;
    }

    private Boolean windCriticalComponentLookup(ArrayList<V1_1.ssurgoAttributes> intersectionAttributeList) throws ServiceException {
        //  Set this to true if we ever set any of the needed values, 
        //otherwise leave false to cause an error generation in the calling code.
        boolean ret_val = false;

        String dom_comp = "";
        String dom_compname = "";
        double ifact = 0.0;
        double tfact = 0.0;
        double sandtotal = 0.0;
        double area = 0.0;

        for (V1_1.ssurgoAttributes attributes : intersectionAttributeList) { //Iterate through all components of this mukey
            if (attributes.gid_area >= (attributes.aoa_area * 0.10)) {
                this.componentLookup(attributes, false);
                if (attributes.horizons.size() > 0) {  //If we have components associated with this mukey
                    for (V1_1.componentAttributes tComponent : attributes.horizons) {  //Iterate through the components of this mukey
                        if ((tComponent.sand_pct >= sandtotal) && (tComponent.sand_pct > 0)) { //Sand content is the main determining factor here..
                            if (sandtotal == tComponent.sand_pct) { // compare sizes of this component area to others since sandtotal matches greatest value found so far...
                                if (tComponent.area_pct > area) {
                                    sandtotal = tComponent.sand_pct;
                                    ifact = tComponent.ifact;
                                    tfact = tComponent.tfact;
                                    dom_comp = tComponent.cokey;
                                    dom_compname = tComponent.compname;
                                    area = tComponent.area_pct;
                                    ret_val = true;
                                }
                            } else { // Use this compoenent because its sandtotal is greater than the last...
                                sandtotal = tComponent.sand_pct;
                                ifact = tComponent.ifact;
                                tfact = tComponent.tfact;
                                dom_comp = tComponent.cokey;
                                dom_compname = tComponent.compname;
                                area = tComponent.area_pct;
                                ret_val = true;
                            }
                        }
                    }
                } //Else no components with this mukey that match the search criteria found in "componentLookup()"
            }
        }

        this.aoa_dom_wind_comp = dom_comp;
        this.aoa_dom_wind_compname = dom_compname;
        this.aoa_ifactor = ifact;
        this.aoa_wind_tfactor = tfact;

        if (dom_comp.isEmpty()) {
            throw new ServiceException(" Could not find any wind critical components/horizons in the database meeting the selection criteria to calculate wind erosion potential.");
        }

        return ret_val;
    }

    private Boolean waterCriticalComponentLookup(ArrayList<V1_1.ssurgoAttributes> intersectionAttributeList) throws ServiceException {
        boolean ret_val = false;
        String dom_comp = "";
        String dom_compname = "";
        double ifact = 0.0;
        double lsfactor = 0.0;
        double tfactor = 0.0;
        double kffact = 0.0;
        double area = 0.0;

        for (V1_1.ssurgoAttributes attributes : intersectionAttributeList) {
            if (attributes.gid_area >= (attributes.aoa_area * 0.10)) {
                
                //Find all components and their horizons for this mukey
                this.componentLookup(attributes, true);
                //Iterate through the components and horizons
                if (attributes.horizons.size() > 0) {
                    for (V1_1.componentAttributes tComponent : attributes.horizons) {
                        if ((tComponent.kffact >= kffact) && (tComponent.kffact > 0)) {
                            if (kffact == tComponent.kffact) {
                                if (tComponent.area_pct > area) {
                                    kffact = tComponent.kffact;
                                    lsfactor = tComponent.lsfact;
                                    tfactor = tComponent.tfact;
                                    ifact = tComponent.ifact;
                                    dom_comp = tComponent.cokey;
                                    dom_compname = tComponent.compname;
                                    area = tComponent.area_pct;
                                    ret_val = true;
                                }
                            } else {
                                kffact = tComponent.kffact;
                                lsfactor = tComponent.lsfact;
                                tfactor = tComponent.tfact;
                                ifact = tComponent.ifact;
                                dom_comp = tComponent.cokey;
                                dom_compname = tComponent.compname;
                                area = tComponent.area_pct;
                                ret_val = true;
                            }
                        }
                    }
                }
            }
        }

        this.aoa_dom_comp = dom_comp;
        this.aoa_dom_compname = dom_compname;
        this.aoa_lsfactor = lsfactor;
        this.aoa_tfactor = tfactor;
        this.aoa_ifactor = ifact;
        this.aoa_kfactor = kffact;

        if (dom_comp.isEmpty()) {
            throw new ServiceException(" Could not find any water critical components/horizons in the database meeting the selection criteria to calculate water erosion potential.");
        }

        return ret_val;
    }

    private PGTools.Centroid getCentroid(String polygon, SessionLogger logger) throws SQLException, ServiceException {
        PGTools.Centroid cent = new PGTools.Centroid();
        try (Connection c = getResourceJDBC(CFACTOR_ID);
                Statement s = c.createStatement();) {
            String find_centroid = "select (" + polygon + ".STCentroid().STY) as lat, "
                    + "(" + polygon + ".STCentroid().STX) as long;";
            try (ResultSet r = s.executeQuery(find_centroid)) {
                logger.info("query sql=" + find_centroid);
                if (r.getMetaData().getColumnCount() != 2) {
                    logger.severe("invalid columns in getCentroid query");
                    throw new ServiceException("invalid columns in getCentroid query");
                }
                logger.info("getting centroid for polygon");
                if (r.next()) {
                    cent.lat = r.getString(1);
                    cent.lon = r.getString(2);
                }
            }
        }
        return cent;
    }

    //  Take a mukey and lookup all the critical components and their horizons data...
    private Boolean componentLookup(V1_1.ssurgoAttributes attribute, boolean useKFact) throws ServiceException {
        String query;
        ResultSet results;
        Boolean ret_val = true;
        double theta = 0.0;
        double lambda = 0.0;
        double length_m = 0.0;
        // Look up tables for slope length 
        double[][] LightleWeesiesSlopeLength = {{0, .75, 100},
        {.75, 1.5, 200},
        {1.5, 2.5, 300},
        {2.5, 3.5, 200},
        {3.5, 4.5, 180},
        {4.5, 5.5, 160},
        {5.5, 6.5, 150},
        {6.5, 7.5, 140},
        {7.5, 8.5, 130},
        {8.5, 9.5, 125},
        {9.5, 10.5, 120},
        {10.5, 11.5, 110},
        {11.5, 12.5, 100},
        {12.5, 13.5, 90},
        {13.5, 14.5, 80},
        {14.5, 15.5, 70},
        {15.5, 17.5, 60},
        {17.5, -1, 50}};

        double[][] PalouseSlopeLength = {{0, 5.5, 350},
        {5.5, 10.5, 275},
        {10.5, 15.5, 225},
        {15.5, 20.5, 175},
        {20.5, 25.5, 150},
        {25.5, 35.5, 125},
        {35.5, -1, 100}};
        double[][] slopeLength;

        if ((null != attribute.mukey) && !attribute.mukey.isEmpty()) {
            try (Connection conn = getResourceJDBC(CFACTOR_ID);
                    Statement statement = conn.createStatement();) {

                query = "SELECT component.cokey, component.compname, component.wei as ifact, component.slope_r, "
                        + " component.slopelenusle_r as lsfact, component.tfact, component.comppct_r, "
                        + " chorizon.cokey, chorizon.kffact, chorizon.kwfact, "
                        + ((useKFact) ? "" : " chorizon.sandtotal_r, chorizon.om_r, chorizon.hzdepb_r, ")
                        + " component.taxorder FROM ssurgo.component "
                        + " INNER JOIN ssurgo.chorizon on component.cokey=chorizon.cokey "
                        + " WHERE component.mukey='" + attribute.mukey + "' "
                        + " AND component.slope_r is not null"
                        + " AND UPPER(component.majcompflag)='YES' "
                        + ((useKFact) ? " AND (component.taxorder!='' AND component.taxorder is not null) ORDER BY component.cokey, chorizon.hzdept_r "
                                : "  AND (component.taxorder!='' AND component.taxorder is not null) "
                        + " AND (chorizon.sandtotal_r is not null OR ( (UPPER(taxorder)='HISTOSOLS') OR ( (UPPER(taxorder)<>'HISTOSOLS') AND hzdepb_r>10 AND om_r>15 )  ))"
                        + " ORDER BY component.cokey, chorizon.hzdept_r") + ";";

                LOG.info(" ssurgo query=" + query);

                results = statement.executeQuery(query);
                LOG.info(" past the query execute");

                attribute.horizons = new ArrayList<>();

                boolean foundKffact = false;
                boolean foundSandLayer = false;
                String lastCokey = "";

                while (results.next()) {
                    V1_1.componentAttributes tHorizon = new V1_1.componentAttributes();

                    tHorizon.cokey = results.getString("cokey");
                    tHorizon.compname = results.getString("compname");
                    tHorizon.ifact = results.getDouble("ifact");
                    tHorizon.tfact = results.getDouble("tfact");
                    double slope_r = results.getDouble("slope_r");
                    tHorizon.slope_r = slope_r;
                    tHorizon.area_pct = (results.getDouble("comppct_r") / 100.0) * (attribute.gid_area / attribute.aoa_area);

                    if (useKFact) { // Water Erodibility needed.
                        String taxorder = results.getString("taxorder");
                        if (!lastCokey.equalsIgnoreCase(tHorizon.cokey)) {
                            lastCokey = tHorizon.cokey;
                            foundKffact = false;
                        }
                        if (!foundKffact) {
                            if (this.isPalouse) {
                                slopeLength = PalouseSlopeLength;
                            } else {
                                slopeLength = LightleWeesiesSlopeLength;
                            }

                            // Calculate lambda
                            for (int i = 0; i < slopeLength.length; i++) {
                                if (slopeLength[i][1] != -1) {
                                    if ((slope_r >= slopeLength[i][0]) && (slope_r < slopeLength[i][1])) {
                                        lambda = slopeLength[i][2];
                                        break;
                                    } else if (slope_r >= slopeLength[i][0]) {
                                        lambda = slopeLength[i][2];
                                        break;
                                    }
                                }
                            }

                            length_m = ((slope_r >= 5.0) ? 0.5 : (slope_r >= 3.5) ? 0.4 : (slope_r >= 1.0) ? 0.3 : 0.2);
                            theta = Math.atan(slope_r / 100.0);

                            tHorizon.lsfact = Math.pow((lambda / 72.6), length_m) * (65.41 * Math.pow(Math.sin(theta), 2.0) + 4.56 * Math.sin(theta) + 0.065);  //results.getDouble("lsfact");
                            tHorizon.kffact = results.getDouble("kffact");
                            if (results.wasNull()) {
                                tHorizon.kffact = results.getDouble("kwfact");
                                if (results.wasNull()) {
                                    if (taxorder.equalsIgnoreCase("histosols")) {
                                        tHorizon.kffact = 0.02;
                                        foundKffact = true;
                                        attribute.horizons.add(tHorizon);
                                        ret_val = true;
                                    }
                                } else {
                                    foundKffact = true;
                                    attribute.horizons.add(tHorizon);
                                    ret_val = true;
                                }
                            } else {
                                foundKffact = true;
                                attribute.horizons.add(tHorizon);
                                ret_val = true;
                            }
                        } else {
                            break;
                        }
                    } else {  //Wind Erodability needed

                        if (!lastCokey.equalsIgnoreCase(tHorizon.cokey)) {
                            lastCokey = tHorizon.cokey;
                            foundSandLayer = false;
                        }
                        if (!foundSandLayer) {
                            tHorizon.sand_pct = results.getDouble("sandtotal_r");
                            tHorizon.taxorder = results.getString("taxorder");
                            tHorizon.hzdepb_r = results.getDouble("hzdepb_r");
                            tHorizon.om_r = results.getDouble("om_r");
                            if (tHorizon.taxorder.equalsIgnoreCase("histosols") || ( !tHorizon.taxorder.equalsIgnoreCase("histosols") && (tHorizon.hzdepb_r > 10) && (tHorizon.om_r > 15))) {
                                tHorizon.sand_pct = 50;
                            }
                            if (tHorizon.sand_pct > 0.0) {
                                attribute.horizons.add(tHorizon);
                                foundSandLayer = true;
                                ret_val = true;
                            }
                        }
                    }
                }

                if (useKFact && !foundKffact) {
                    ret_val = false;
                    LOG.warning("No kffact found for this soil component in horizon data.");
                }

                if (attribute.horizons.size() <= 0) {
                    ret_val = false;
                    LOG.warning("No suitable soil components and/or horizon data found for this mukey: " + attribute.mukey + " .");
                }

            } catch (SQLException ex) {
                throw new ServiceException("Could not continue processing the component lookups for this mapunit intersect:  " + ex.getMessage(), ex);
            }
        }

        return ret_val;

    }
    //Inner Classes/Structures go here...

    /**
     *
     */
    //Mukey level
    public static class ssurgoAttributes {

        public String gid;
        public String AoA_Id;
        public String mukey;
        public double gid_area;
        public double aoa_area;

        public ArrayList<V1_1.componentAttributes> horizons;

        public ssurgoAttributes() {
            this.gid = "";
            this.AoA_Id = "";
            this.mukey = "";
            this.gid_area = 0.0;
            this.aoa_area = 1.0;
            this.horizons = null;
        }
    }

//  components of the above mukey (ssurgoAttributes)
    public static class componentAttributes {

        public String cokey;
        public String compname;
        public double ifact;
        public double slope_r;
        public double tfact;
        public double kffact;
        public double kwfact;
        public double area_pct;
        public double lsfact;
        public double sand_pct;
        public double om_r;
        public String taxorder;
        public double hzdepb_r;

        public componentAttributes() {
            this.cokey = "";
            this.compname = "";
            this.ifact = 0.0;
            this.slope_r = 0.0;
            this.tfact = 0.0;
            this.kffact = 0.0;
            this.kwfact = 0.0;
            this.area_pct = 0.0;
            this.lsfact = 0.0;
            this.sand_pct = 0.0;
            this.om_r = 0.0;
            this.taxorder = "";
            this.hzdepb_r = 0.0;
        }
    }

}