NASIS.java [src/java/d/dataNodes] Revision: default  Date:
/*
 * $Id$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API, and application suite.
 *
 * 2012-2017, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package d.dataNodes;

import d.soils.wwe02_wepssoilinput.StratifiedTextures;
import csip.SessionLogger;
import d.util.OrganicSoilException;
import d.util.ConfigData;
import d.util.SoilUtil;
import d.util.Utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import javax.measure.Measure;
import javax.measure.quantity.Length;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;

/**
 *
 * @author brad
 */
public class NASIS {
    private SessionLogger LOG;
    private static boolean estimateNullValues = true;
    private static final String STRATIFIED_LAYER_KEY = "SR";
    private String textureChk[] = {"by", "cem", "ce", "dur", "frag", "gyp", "hpm", "ind", "marl", "mat",
        "mpm", "muck", "mpt", "or", "opwd",
        "peat", "pc", "pf", "pgp", "pum", "spm", "st", "udom",
        "u", "uwb", "var", "w", "wb", "cem-", "pf-"};
    private static final String[] ORGANIC_TEXTURES = {"mpm", "mpt", "muck", "peat", "spm", "udom", "pdom", "hpm"};
        
    private String errIncFile = "";
    private String state = "";
    private String county = "";
    private String ssaname = "";
    private String ssaid = "";
    private String musym = "";
    private String compname = "";
    private String comppct = "";
    private String taxorder = "";
    private String localphase = "";
    private double losstolerance = 0.0;
    private double slope = 0.0;
    private double albedodry = 0.0;
    
    private int numlay = 0;
    private int adjlay = 0;
    private double hzdept[];
    private double hzdepb[];
    private double hzthk[];
    private String texture[];
    private boolean stratified[];
    private boolean rv[];
    private double claytotal[];
    private double sandtotal[];
    private double silttotal[];
    private double sandvco[];
    private double sandco[];
    private double sandmed[];
    private double sandfine[];
    private double sandvf[];
    private double dbthirdbar[];
    private double wtenthbar[];
    private double wthirdbar[];
    private double w15thbar[];
    private double ksat[];
    private double cec7[];
    private double ecec[];
    private double om[];
    private double caco3[];
    private double ph1to1h2o[];
    private double ph01mcacl2[];
    private double fragvol[];
    private double lep[];
    private double bedrockDepth;
    private double impermiableDepth;
    private static double omFractionThreshold;
    
    private enum SoilElementTestAction {

        IGNORE, ERROR, ESTIMATE
    }
    
    public NASIS(int inpnumlay,SessionLogger log) {
        LOG = log;
        numlay = inpnumlay;
        if (numlay == 0) {
            return;
        }

        hzdept = new double[numlay];
        hzdepb = new double[numlay];
        hzthk = new double[numlay];
        texture = new String[numlay];
        stratified = new boolean[numlay];
        rv = new boolean[numlay];
        claytotal = new double[numlay];
        sandtotal = new double[numlay];
        silttotal = new double[numlay];
        sandvco = new double[numlay];
        sandco = new double[numlay];
        sandmed = new double[numlay];
        sandfine = new double[numlay];
        sandvf = new double[numlay];
        dbthirdbar = new double[numlay];
        wtenthbar = new double[numlay];
        wthirdbar = new double[numlay];
        w15thbar = new double[numlay];
        ksat = new double[numlay];
        cec7 = new double[numlay];
        ecec = new double[numlay];
        om = new double[numlay];
        caco3 = new double[numlay];
        ph1to1h2o = new double[numlay];
        ph01mcacl2 = new double[numlay];
        fragvol = new double[numlay];
        lep = new double[numlay];

    }

    public SessionLogger getLOG() {
        return LOG;
    }

    public void setLOG(SessionLogger LOG) {
        this.LOG = LOG;
    }

    public static boolean isEstimateNullValues() {
        return estimateNullValues;
    }

    public static void setEstimateNullValues(boolean estimateNullValues) {
        NASIS.estimateNullValues = estimateNullValues;
    }

    public String[] getTextureChk() {
        return textureChk;
    }

    public void setTextureChk(String[] textureChk) {
        this.textureChk = textureChk;
    }

    public String getErrIncFile() {
        return errIncFile;
    }

    public void setErrIncFile(String errIncFile) {
        this.errIncFile = errIncFile;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getCounty() {
        return county;
    }

    public void setCounty(String county) {
        this.county = county;
    }

    public String getSsaname() {
        return ssaname;
    }

    public void setSsaname(String ssaname) {
        this.ssaname = ssaname;
    }

    public String getSsaid() {
        return ssaid;
    }

    public void setSsaid(String ssaid) {
        this.ssaid = ssaid;
    }

    public String getMusym() {
        return musym;
    }

    public void setMusym(String musym) {
        this.musym = musym;
    }

    public String getCompname() {
        return compname;
    }

    public void setCompname(String compname) {
        this.compname = compname;
    }

    public String getComppct() {
        return comppct;
    }

    public void setComppct(String comppct) {
        this.comppct = comppct;
    }

    public String getTaxorder() {
        return taxorder;
    }

    public void setTaxorder(String taxorder) {
        this.taxorder = taxorder;
    }

    public String getLocalphase() {
        return localphase;
    }

    public void setLocalphase(String localphase) {
        this.localphase = localphase;
    }

    public double getLosstolerance() {
        return losstolerance;
    }

    public void setLosstolerance(double losstolerance) {
        this.losstolerance = losstolerance;
    }

    public double getSlope() {
        return slope;
    }

    public void setSlope(double slope) {
        this.slope = slope;
    }

    public double getAlbedodry() {
        return albedodry;
    }

    public void setAlbedodry(double albedodry) {
        this.albedodry = albedodry;
    }

    public int getNumlay() {
        return numlay;
    }

    public void setNumlay(int numlay) {
        this.numlay = numlay;
    }

    public int getAdjlay() {
        return adjlay;
    }

    public void setAdjlay(int adjlay) {
        this.adjlay = adjlay;
    }

    public double[] getHzdept() {
        return hzdept;
    }

    public void setHzdept(double[] hzdept) {
        this.hzdept = hzdept;
    }

    public double[] getHzdepb() {
        return hzdepb;
    }

    public void setHzdepb(double[] hzdepb) {
        this.hzdepb = hzdepb;
    }

    public double[] getHzthk() {
        return hzthk;
    }

    public void setHzthk(double[] hzthk) {
        this.hzthk = hzthk;
    }

    public String[] getTexture() {
        return texture;
    }

    public void setTexture(String[] texture) {
        this.texture = texture;
    }

    public boolean[] getStratified() {
        return stratified;
    }

    public void setStratified(boolean[] stratified) {
        this.stratified = stratified;
    }

    public boolean[] getRv() {
        return rv;
    }

    public void setRv(boolean[] rv) {
        this.rv = rv;
    }

    public double[] getClaytotal() {
        return claytotal;
    }

    public void setClaytotal(double[] claytotal) {
        this.claytotal = claytotal;
    }

    public double[] getSandtotal() {
        return sandtotal;
    }

    public void setSandtotal(double[] sandtotal) {
        this.sandtotal = sandtotal;
    }

    public double[] getSilttotal() {
        return silttotal;
    }

    public void setSilttotal(double[] silttotal) {
        this.silttotal = silttotal;
    }

    public double[] getSandvco() {
        return sandvco;
    }

    public void setSandvco(double[] sandvco) {
        this.sandvco = sandvco;
    }

    public double[] getSandco() {
        return sandco;
    }

    public void setSandco(double[] sandco) {
        this.sandco = sandco;
    }

    public double[] getSandmed() {
        return sandmed;
    }

    public void setSandmed(double[] sandmed) {
        this.sandmed = sandmed;
    }

    public double[] getSandfine() {
        return sandfine;
    }

    public void setSandfine(double[] sandfine) {
        this.sandfine = sandfine;
    }

    public double[] getSandvf() {
        return sandvf;
    }

    public void setSandvf(double[] sandvf) {
        this.sandvf = sandvf;
    }

    public double[] getDbthirdbar() {
        return dbthirdbar;
    }

    public void setDbthirdbar(double[] dbthirdbar) {
        this.dbthirdbar = dbthirdbar;
    }

    public double[] getWtenthbar() {
        return wtenthbar;
    }

    public void setWtenthbar(double[] wtenthbar) {
        this.wtenthbar = wtenthbar;
    }

    public double[] getWthirdbar() {
        return wthirdbar;
    }

    public void setWthirdbar(double[] wthirdbar) {
        this.wthirdbar = wthirdbar;
    }

    public double[] getW15thbar() {
        return w15thbar;
    }

    public void setW15thbar(double[] w15thbar) {
        this.w15thbar = w15thbar;
    }

    public double[] getKsat() {
        return ksat;
    }

    public void setKsat(double[] ksat) {
        this.ksat = ksat;
    }

    public double[] getCec7() {
        return cec7;
    }

    public void setCec7(double[] cec7) {
        this.cec7 = cec7;
    }

    public double[] getEcec() {
        return ecec;
    }

    public void setEcec(double[] ecec) {
        this.ecec = ecec;
    }

    public double[] getOm() {
        return om;
    }

    public void setOm(double[] om) {
        this.om = om;
    }

    public double[] getCaco3() {
        return caco3;
    }

    public void setCaco3(double[] caco3) {
        this.caco3 = caco3;
    }

    public double[] getPh1to1h2o() {
        return ph1to1h2o;
    }

    public void setPh1to1h2o(double[] ph1to1h2o) {
        this.ph1to1h2o = ph1to1h2o;
    }

    public double[] getPh01mcacl2() {
        return ph01mcacl2;
    }

    public void setPh01mcacl2(double[] ph01mcacl2) {
        this.ph01mcacl2 = ph01mcacl2;
    }

    public double[] getFragvol() {
        return fragvol;
    }

    public void setFragvol(double[] fragvol) {
        this.fragvol = fragvol;
    }

    public double[] getLep() {
        return lep;
    }

    public void setLep(double[] lep) {
        this.lep = lep;
    }

    public double getBedrockDepth() {
        return bedrockDepth;
    }

    public void setBedrockDepth(double bedrockDepth) {
        this.bedrockDepth = bedrockDepth;
    }

    public double getImpermiableDepth() {
        return impermiableDepth;
    }

    public void setImpermiableDepth(double impermiableDepth) {
        this.impermiableDepth = impermiableDepth;
    }

    public static double getOmFractionThreshold() {
        return omFractionThreshold;
    }

    public static void setOmFractionThreshold(double omFractionThreshold) {
        NASIS.omFractionThreshold = omFractionThreshold;
    }
    
    
    
        public boolean loadSQL(Connection con, String cokey) {
        try {

            String mukey = null;
            String lkey = null;
            String chkey = null;

            Statement stmt = con.createStatement();
//			String query = new String("SELECT * FROM component WHERE cokey = '"+cokey+"'");
            String query = "SELECT compname,comppct_r,taxorder,slope_r,albedodry_r,mukey,localphase,"
                    + "tfact FROM component WHERE cokey = '" + cokey + "'";

            ResultSet rs = stmt.executeQuery(query);
            boolean tmp = rs.next();
            if (!tmp)
                return false;
            
            copyComponent(rs);

            //can test taxorder now to see if organic
            // We have our own organic test later so just go through with this
//            if (ConfigData.getDefault(LOG).getSoilTestOrganic()) {
//                if ("histosols".equalsIgnoreCase(taxorder)) {
//                    throw new OrganicSoilException();
//                }
//            }

            mukey = rs.getString("mukey");
            rs.close();

            query = "SELECT musym,lkey FROM mapunit WHERE mukey = '" + mukey + "'";

            rs = stmt.executeQuery(query);
            tmp = rs.next();
            copyMapUnit(rs);

            lkey = rs.getString("lkey");
            rs.close();

            //Bedrock Depth
            query = "SELECT brockdepmin AS brockdepmin_r FROM muaggatt WHERE mukey = '" + mukey + "'";
            rs = stmt.executeQuery(query);
            tmp = rs.next();
            copyMuAggatt(rs);
            rs.close();

            //Restritive Depth
            query = "SELECT Min(resdept_r) AS resdeptmin_r FROM  corestrictions WHERE cokey='" + cokey + "' GROUP BY cokey";
            rs = stmt.executeQuery(query);
            tmp = rs.next();
            copyCoRestrictions(rs);

            rs.close();

            try {
                query = "SELECT areaname,areasymbol,cordate,ssurgoarchived,"
                        + "tabularversion FROM legend WHERE lkey = '" + lkey + "'";
                rs = stmt.executeQuery(query);
            } catch (SQLException sqle) {
                query = "SELECT areaname,areasymbol,cordate,ssurgoarchived FROM legend WHERE lkey = '" + lkey + "'";
                rs = stmt.executeQuery(query);
            }
            tmp = rs.next();
            copyLegend(rs);

            rs.close();

            Statement stmtFrags = con.createStatement();
            Statement stmtTexture = con.createStatement();
//			query = new String("SELECT * FROM chorizon WHERE cokey = '"+cokey+"'");

            String[] hrzFlds = {"hzdept", "hzdepb", "claytotal", "sandtotal", "silttotal",
                "sandvc", "sandco", "sandmed", "sandfine",
                "sandvf", "dbthirdbar", "wtenthbar",
                "wthirdbar", "wfifteenbar", "cec7", "ecec", "om",
                "caco3", "ph1to1h2o", "ph01mcacl2", "lep", "ksat"};
            StringBuilder sb = new StringBuilder("SELECT ");
            for (String hrzFld : hrzFlds) {
                sb.append(hrzFld + "_r,");
                sb.append(hrzFld + "_l,");
                sb.append(hrzFld + "_h,");
            }
            //hzdept_r, (hzdept_l + hzdept_h) / 2
            sb.append("chkey, desgnmaster, hzname FROM chorizon WHERE cokey = '" + cokey + "' ORDER BY hzdept_r ASC");
            // Order by depth to make sure layers are in the correct order.

            String chorizonQuery = sb.toString();
            rs = stmt.executeQuery(chorizonQuery);
            int layerNum = 0;
            int i = 0;

            boolean foundAMineralLayer = false;

            //up to this amount is allowed to be ignored
            double maxOrganicDepth = ConfigData.getDefault(LOG).getSoilMaxOrganicDepth().doubleValue(SI.MILLIMETER);

            //as we loop over the layers, we add up how much organic layer has been ignored
            double usedOrganicDepth = 0;
            while (rs.next()) {
                chkey = rs.getString("chkey");

                String chtexturegrpQuery = "SELECT texture, stratextsflag, rvindicator FROM chtexturegrp WHERE chkey = '"
                        + chkey + "'";
                try (ResultSet rsTexture = stmtTexture.executeQuery(chtexturegrpQuery)) {

                    //do we ignore an organic surface?
                    if (ConfigData.getDefault(LOG).isSkipOrganicSoilSurfaceLayers()
                            && !foundAMineralLayer && usedOrganicDepth <= maxOrganicDepth) {

                        String hzname = getValueFromRS(rs, "hzname", null);
                        String desgnmaster = getValueFromRS(rs, "desgnmaster", null);
                        double om = getValueFromRS(rs, "om");
                        //convert from percent to fraction
                        om = om / 100d;
                        String text = getValueFromRS(rsTexture, "texture", null);

                        boolean organic = isLayerOrganic(hzname, desgnmaster, text, om);

                        if (organic) {
                            //data mart is in cm not mm
                            double thicknessMiliMeter
                                    = getValueFromRS(rs, "hzthk", getValueFromRS(rs, "hzdepb", Double.NaN)) * 10;

                            usedOrganicDepth += thicknessMiliMeter;
                            if (usedOrganicDepth <= maxOrganicDepth) {
                                //still allowed to ignore

                                LOG.log(Level.INFO,"Organic layer ignored.");
                                continue;
                            } else {
                                Measure<Double, Length> max = Measure.valueOf(maxOrganicDepth, SI.MILLIMETER);
                                Measure<Double, Length> used = Measure.valueOf(usedOrganicDepth, SI.MILLIMETER);

                                NumberFormat format = DecimalFormat.getNumberInstance();

                                boolean isSIUnits = Utils.SIUnits.equals(ConfigData.getDefault(LOG).getData(ConfigData.Units));

                                Unit<Length> units = isSIUnits ? SI.MILLIMETER : NonSI.INCH;

                                if (i == 0) {
                                    throw new OrganicSoilException(
                                            "The organic surface layer depth exceeded the maximum ignorable depth.\n"
                                            + format.format(used.doubleValue(units)) + units + " > "
                                            + format.format(max.doubleValue(units)) + units);
                                } else {
                                    throw new OrganicSoilException(
                                            "The soil layer below the ignored organic layer is still organic.");
                                }

                            }

                        } else {
                            //not organic, so we're good to go
                            foundAMineralLayer = true;
                        }

                    }

                    copyHorizon(rs, layerNum);

                    String chfragsQuery = "SELECT fragvol_r,fragvol_h,fragvol_l FROM chfrags WHERE chkey = '" + chkey + "'";
                    ResultSet rsFrags = stmtFrags.executeQuery(chfragsQuery);
                    copyFrags(rsFrags, layerNum);
                    rsFrags.close();

                    //already executed the query for the organic tests
                    copyTexture(rsTexture, layerNum);
                    layerNum++;
                } finally {
                    i++;
                }
            }
            rs.close();
            this.numlay = layerNum;

            //If the config option is set, average the stratified layers.
            if (ConfigData.getDefault(LOG).isAverageStratifiedSoilLayers()) {
                averageStratifiedLayers();
            }

            fixUpData();

            testData();

        } catch (SQLException e) {
            e.printStackTrace();
//			for (; e != null; e = e.getNextException()) {
//				//System.err.println("\n" + e);
//				e.printStackTrace();
//			}
        }

        return true;
    }
        
        public void fixUpData() {
        for (adjlay = 1; adjlay < numlay; adjlay++) {

            // stop if layer is identified as rock, peat, etc.
            if (chkTexture(texture[adjlay])) {
                break;
            }

            // stop if both sand and clay are missing or zero
            if ((Double.isNaN(claytotal[adjlay]) || claytotal[adjlay] == 0.0)
                    && (Double.isNaN(sandtotal[adjlay]) || sandtotal[adjlay] == 0.0)) {
                break;
            }
            // stop if layer does not have wet bulk density
            if (Double.isNaN(dbthirdbar[adjlay]) || dbthirdbar[adjlay] == 0.0) {
                break;
            }
        }

    }
        
    public void testData() {

        SoilElementTestAction ignore = SoilElementTestAction.IGNORE;
        SoilElementTestAction ignore2 = SoilElementTestAction.IGNORE;
        SoilElementTestAction estimate = SoilElementTestAction.ESTIMATE;
        SoilElementTestAction error = SoilElementTestAction.ERROR;

        if (!estimateNullValues) {
            //We don't want to estimate
            estimate = error;
            ignore2 = error;

            //Test values that must be present in surgo
            state = testStringSoilElement(state, "State", "state", SoilElementTestAction.IGNORE, "unknown");

        }

        //Errors
        if (numlay <= 0) {
            LOG.log(Level.SEVERE, "No layers found in soil record.");
        }

        //Soil Components
        compname = testStringSoilElement(compname, "Component Name", "compname", ignore, "unknown");
        comppct = testStringSoilElement(comppct, "Component Percent", "comppct_r", ignore, "unknown");
        taxorder = testStringSoilElement(taxorder, "Taxonomic Order", "taxorder", ignore, "unknown");
        localphase = testStringSoilElement(localphase, "Local Phase", "localphase", ignore, "unknown");
        losstolerance = testNumericSoilElement(losstolerance, "t/ac/yr",
                1, 5, "Soil Loss Tolerance", "losstolerance", ignore, -1);
        slope = testNumericSoilElement(slope, "fraction", 0, 999, "Slope Gradient", "slope", ignore, -1);
        albedodry = testNumericSoilElement(albedodry, "fraction", 0, 1, "Dry Soil Albedo",
                "albedodry", estimate, 0.6 / Math.exp(0.4 * om[0]));
        //End Soil Components

        //Map Unit
        musym = testStringSoilElement(musym, "Map Symbol", "musym", ignore2, "no map symbol");
        //End Map Unit

        //MuAggatt
        bedrockDepth = testNumericSoilElement(bedrockDepth, "cm", 0, 9999, "Bedrock Depth", "bedrockDepth", ignore, 9999);
        //End MuAggatt

        //CoRestrictions
        impermiableDepth = testNumericSoilElement(impermiableDepth, "", 0, 9999,
                "Impermiable/Restrictive Depth", "resdept_r_min", ignore, 9999);
        //End CoRestrictions

        //Legend
        ssaname = testStringSoilElement(ssaname, "Area Name/Area State", "areaname", ignore2, "no area name, no area state");
        ssaid = testStringSoilElement(ssaid, "Area Symbol", "areasymbol", ignore2, "no area symbol");
        //End Legend

        //Test Soil Layers, only tests layers that were not dropped for texture reasons.
        if (adjlay <= numlay) {

            for (int layer = 0; layer < adjlay; layer++) {
                hzdept[layer] = testNumericSoilElement(hzdept[layer], "cm", 0, 999,
                        "Layer Depth Top", "hzdept", error, Double.NaN, layer);
                hzdepb[layer] = testNumericSoilElement(hzdepb[layer], "cm", 0, 999,
                        "Layer Depth Bottom", "hzdepb", error, Double.NaN, layer);
                hzthk[layer] = hzdepb[layer] - hzdept[layer];
                hzthk[layer] = testNumericSoilElement(hzthk[layer], "cm", 0.01, 1000,
                        "Layer Thickness", "hzthk", estimate, hzdept[layer] - hzdepb[layer], layer);
                claytotal[layer] = testNumericSoilElement(claytotal[layer], "weight %",
                        0, 100, "Total Clay Content", "claytotal", error, Double.NaN, layer);
                sandtotal[layer] = testNumericSoilElement(sandtotal[layer], "weight %",
                        0, 100, "Total Sand Content", "sandtotal", error, Double.NaN, layer);

                double calculatedSiltTotal = 100 - (claytotal[layer] + sandtotal[layer]);
                silttotal[layer] = testNumericSoilElement(silttotal[layer], "weight %",
                        calculatedSiltTotal * 0.999, calculatedSiltTotal * 1.001,
                        "Total Silt Content", "silttotal", SoilElementTestAction.ESTIMATE, calculatedSiltTotal, layer);

                sandvf[layer] = testNumericSoilElement(sandvf[layer], "weight %", 0, 100,
                        "Very Fine Sand", "sandvf", error, Double.NaN, layer);
                dbthirdbar[layer] = testNumericSoilElement(dbthirdbar[layer], "g/cm3", .02, 2.60,
                        "1/3 Bar Bulk Density", "dbthirdbar", error, Double.NaN, layer);

                //the value will be estimated by ifc
                wthirdbar[layer] = testNumericSoilElement(wthirdbar[layer], "volume %", 0, 80,
                        "1/3 bar water ", "wthirdbar", ignore, Double.NaN, layer);

                om[layer] = testNumericSoilElement(om[layer], "weight %", 0, 100,
                        "Organic Matter Content", "om", error, Double.NaN, layer);
                caco3[layer] = testNumericSoilElement(caco3[layer], "weight %", 0, 110,
                        "CaCO3 Equivalent", "caco3", error, Double.NaN, layer);

                //cec7[layer] = testNumericSoilElement(cec7[layer], "weight %", 0, 110,
                //"CEC7 or ECEC", "cec7", error, Double.NaN, layer);
                if (Double.isNaN(cec7[layer]) && Double.isNaN(ecec[layer])) {
                    LOG.log(Level.SEVERE,"CEC7 and ECEC values are missing from layer " + (layer + 1) + ".");
                }
                //total sand and total clay can not be greater than 100% combined.
                if (claytotal[layer] + sandtotal[layer] > 100) {
                    LOG.log(Level.SEVERE, "Total Clay and Sand content for layer " + (layer + 1) + " exceeds 100%.");
                }

//                fractionRock = (double[]) nasinp.fragvol.clone();				// rock %
//        veryCoarseSandFraction = (double[]) nasinp.sandvco.clone();				// coarseSandFraction
//        coarseSandFraction = (double[]) nasinp.sandco.clone();				// coarseSandFraction
//        mediumSandFraction = (double[]) nasinp.sandmed.clone();				// mediumSandFraction
//        fineSandFraction = (double[]) nasinp.sandfine.clone();				// fineSandFraction
//        veryFineSandFraction = (double[]) nasinp.sandvf.clone();
                if (!estimateNullValues) {
                    //Test values per layer
                    if (Double.isNaN(ph1to1h2o[layer]) && Double.isNaN(ph01mcacl2[layer])) {
                        LOG.log(Level.SEVERE, "pH 1:1 H2O and 0.01 MCaCl2 values are missing from layer " + (layer + 1) + ".");
                    }
                    ksat[layer] = testNumericSoilElement(ksat[layer], "um/s", 0, 705, "Ksat", "ksat", error, Double.NaN, layer);
                    w15thbar[layer] = testNumericSoilElement(w15thbar[layer], "volume %", 0, 70,
                            "15 bar water (wp)", "wfifteenbar", ignore, Double.NaN, layer);
                }
                //Special case for LEP
                if (Double.isNaN(lep[layer])) {
                    if (estimateNullValues) {
                        //Estimate a value

                        double bsd = SoilUtil.estimateSettledBulkDensity(claytotal[layer], sandtotal[layer], om[layer]);
                        if (wthirdbar[layer] > bsd) {
                            bsd = wthirdbar[layer];
                            String msgText = "Settled bulk density adjusted: Set to wet bulk density.";
                            LOG.log(Level.WARNING, msgText);
                        } else {
                            String msgText = "Settled bulk density estimated: " + Utils.formatDouble(bsd, 4);
                            LOG.log(Level.WARNING, msgText);
                        }

                        double lepc = SoilUtil.estimateLinearExtensibility(bsd, wthirdbar[layer]);
                        lep[layer] = testNumericSoilElement(lep[layer], "%", 0, 30,
                                "Linear Extensibility", "lep", estimate, lepc, layer);
                    } else {
                        if (om[layer] > omFractionThreshold) {
                            lep[layer] = testNumericSoilElement(lep[layer], "%", 0, 30,
                                    "Linear Extensibility", "lep", estimate, 0.0d, layer);
                        } else {
                            lep[layer] = testNumericSoilElement(lep[layer], "%", 0, 30,
                                    "Linear Extensibility", "lep", error, Double.NaN, layer);
                        }
                    }
                } else {
                    lep[layer] = testNumericSoilElement(lep[layer], "%", 0, 30, "Linear Extensibility",
                            "lep", error, Double.NaN, layer);
                }

            }
        }
    }
    
    private String testStringSoilElement(String value, String name, String header,
            SoilElementTestAction action, String actionValue) {
//1.  Test is not null
        if (value == null || value.length() == 0) {
            switch (action) {
                case IGNORE:
                    logSubstitutionMessage(Level.WARNING, name, header, actionValue);
                    return actionValue;
                case ESTIMATE:
                    logEstimatedMessage(Level.WARNING, name, header, actionValue);
                    return actionValue;
                case ERROR:
                    logMessage(Level.SEVERE, name + " (" + header + ") is missing.");
                    return actionValue;
            }
        }
        return value;
    }
    
    private double testNumericSoilElement(double value, String units, double minimum,
            double maximum, String name, String header, SoilElementTestAction action, double actionValue) {
        return testNumericSoilElement(value, units, minimum, maximum, name, header, action, actionValue, -1);
    }
    
    private double testNumericSoilElement(double value, String units, double minimum,
            double maximum, String name, String header, SoilElementTestAction action, double actionValue, int layer) {
        
        String layerTag = "";
        if (layer > -1) {
            layerTag = " from layer " + (layer + 1);
        }
//1.  Test is not null
        if (Double.isNaN(value)) {
            switch (action) {
                case IGNORE:
                    logSubstitutionMessage(Level.WARNING, name, header, actionValue, layer);
                    return actionValue;
                case ESTIMATE:
                    logEstimatedMessage(Level.WARNING, name, header, actionValue, layer);
                    return actionValue;
                case ERROR:
                    logMessage(Level.SEVERE, name + " (" + header + ") is missing" + layerTag + ".");
                    return actionValue;
            }
        }
        //2. Test in range if needed
        boolean outOfRange = false;
        if (!Double.isNaN(minimum) && value < minimum) {
            outOfRange = true;
        }
        if (!Double.isNaN(maximum) && value > maximum) {
            outOfRange = true;
        }
        if (outOfRange) {
            switch (action) {
                case IGNORE:
                    logSubstitutionMessage(Level.WARNING, name, header, Double.toString(actionValue) + " " + units, layer);
                    return actionValue;
                case ESTIMATE:
                    logEstimatedMessage(Level.WARNING, name, header, Double.toString(actionValue) + " " + units, layer);
                    return actionValue;
                case ERROR:
                    logMessage(Level.SEVERE, name + " (" + header + ")" + layerTag
                            + " is outside the acceptable range (" + Double.toString(minimum)
                            + " to " + Double.toString(maximum) + ").");
                    return actionValue;
            }
        }
        return value;
    }
        
    public void averageStratifiedLayers() {
        StratifiedTextures st = null;//StratifiedTextures.getInstance(LOG);
        Layers:
        for (int i = 0; i < numlay; i++) {
            String layerTexture = texture[i];
            if (stratified[i] && rv[i] && layerTexture.trim().toUpperCase().startsWith(STRATIFIED_LAYER_KEY)) {
                //This layer is stratified
                String[] textures = layerTexture.split(" ", -1);

                boolean first = true;
                double sand = 0;
                double clay = 0;
                double vsf = 0;
                int count = 0;
                for (String subTexture : textures) {
                    subTexture = subTexture.trim();
                    if (first) {
                        //skip the sr marker
                        first = false;
                        continue;
                    }
                    if (st.isTextureValid(subTexture)) {
                        count++;
                        sand += st.getSand(subTexture);
                        sand /= count;

                        clay += st.getClay(subTexture);
                        clay /= count;

                        vsf += st.getVeryFineSand(subTexture);
                        vsf /= count;
                    } else {
                        //can't fix up this layer
                        continue Layers;
                    }
                }
                if (sand + clay > 1) {
                    //TODO: log error
                    continue Layers;
                }
                double silt = 1 - sand - clay;
                sandtotal[i] = sand * 100;
                silttotal[i] = silt * 100;
                claytotal[i] = clay * 100;

                sandvf[i] = vsf * 100;

                LOG.log(Level.INFO, "Averaged stratified layer values on layer " + String.valueOf(i + 1) + ".");

            }
        }

    }
    
    private boolean chkTexture(String texture) {
        String tmp = texture.toLowerCase();
        for (String textureChk1 : textureChk) {
            if (tmp.indexOf(textureChk1) >= 0) {
                int tdx = tmp.indexOf(textureChk1); // check if token
                if (tdx > 0) {
                    if (!Character.isWhitespace(tmp.charAt(tdx - 1))) {
                        continue;
                    }
                    if (tmp.charAt(tdx - 1) != '-') {
                        continue;
                    }
                }
                if (tdx + textureChk1.length() < tmp.length()) {
                    if (!Character.isWhitespace(tmp.charAt(tdx + textureChk1.length()))) {
                        continue;
                    }
                }
                LOG.log(Level.WARNING, "Layer dropped for texture " + texture + ".");
                return true;
            }
        }
        return false;
    }
        
    private static boolean isLayerOrganic(String hzname, String desgnmaster, String texture, double organicMatter) {
        //horizon name
        if (hzname != null && hzname.startsWith("O")) {
            return true;
        }

        if (desgnmaster != null && desgnmaster.startsWith("O")) {
            return true;
        }

        for (String organicTexture : ORGANIC_TEXTURES) {
            if (organicTexture.equals(texture)) {
                return true;
            }
        }

        if (organicMatter > omFractionThreshold) {
            return true;
        }

        //aka, a mineral layer
        return false;
    }
    private void copyComponent(ResultSet rs) {
        compname = getValueFromRS(rs, "compname", null);
        comppct = getValueFromRS(rs, "comppct_r", null);
        taxorder = getValueFromRS(rs, "taxorder", null);
        localphase = getValueFromRS(rs, "localphase", "");
        String temp = getValueFromRS(rs, "tfact", "0");
        if (temp.trim().length() == 0) {
            temp = "0";
        }
        losstolerance = Integer.parseInt(temp);
//            losstolerance = getValueFromRS(rs, "tfact");
        slope = getValueFromRS(rs, "slope");
        albedodry = getValueFromRS(rs, "albedodry");
    }    
    
    protected void copyMapUnit(ResultSet rs) {
        musym = getValueFromRS(rs, "musym", null);
    }

    /**
     *
     * @param rs
     */
    protected void copyMuAggatt(ResultSet rs) {
        bedrockDepth = getValueFromRS(rs, "brockdepmin");
    }

    /**
     *
     * @param rs
     */
    protected void copyCoRestrictions(ResultSet rs) {
        impermiableDepth = getValueFromRS(rs, "resdeptmin");
    }

    /********************************************************************** wjr
     * @param rs */
    protected void copyLegend(ResultSet rs) {
        ssaname = getValueFromRS(rs, "areaname", null);
        ssaid = getValueFromRS(rs, "areasymbol", null);
        Date cordate = getDateValueFromRS(rs, "cordate");

        Date archiveddate = getDateValueFromRS(rs, "ssurgoarchived");
        String tabularVersion = getStringValueFromRS(rs, "tabularversion");

        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");

        if (cordate != null) {
            LOG.log(Level.INFO, "Correlation Date: " + format.format(cordate));
        }
        if (archiveddate != null) {
            LOG.log(Level.INFO, "SSURGO Archived Date: " + format.format(archiveddate));
        }

        if (tabularVersion != null && tabularVersion.trim().length() > 0) {
            LOG.log(Level.INFO, "Tabular Version: " + tabularVersion);
        }

        String[] parts = ssaname.split(",", -1);
        if (parts.length == 0) {
            state = null;
            county = "";
        } else if (parts.length == 1) {
            state = parts[0].trim();
            county = "";
        } else {
            county = parts[0].trim();
            state = parts[1].trim();
        }
    }
    
    protected void copyHorizon(ResultSet rs, int idx) {
        this.hzdept[idx] = getValueFromRS(rs, "hzdept");
        this.hzdepb[idx] = getValueFromRS(rs, "hzdepb");
        this.claytotal[idx] = getValueFromRS(rs, "claytotal");
        this.sandtotal[idx] = getValueFromRS(rs, "sandtotal");
        this.silttotal[idx] = getValueFromRS(rs, "silttotal");
        sandvco[idx] = getValueFromRS(rs, "sandvc", 0);
        sandco[idx] = getValueFromRS(rs, "sandco", 0);
        this.sandmed[idx] = getValueFromRS(rs, "sandmed", 0);
        this.sandfine[idx] = getValueFromRS(rs, "sandfine", 0);
        this.sandvf[idx] = getValueFromRS(rs, "sandvf");
        this.dbthirdbar[idx] = getValueFromRS(rs, "dbthirdbar");
//		this.dbovendry[idx] = getValueFromRS(rs, "dbovendry");
//		this.wtenthbar[idx] = getValueFromRS(rs, "wtenthbar");
        //this.wtenthbar[idx] = -9.9;
        this.wthirdbar[idx] = getValueFromRS(rs, "wthirdbar");
        this.w15thbar[idx] = getValueFromRS(rs, "wfifteenbar");
        this.ksat[idx] = getValueFromRS(rs, "ksat");

        this.cec7[idx] = getValueFromRS(rs, "cec7");
        this.ecec[idx] = getValueFromRS(rs, "ecec");
        // I think my error checking will work - it compiles :-)
        // However, my test case error is not because both fields are null,
        // but that one is null for one layer and the other is null for another and
        // the NASIS(SSURGO) to IFC code can't handle that correctly (yet) - LEW
        //if ((this.cec7[idx] < 0.0) && (this.ecec[idx] < 0.0)) 
        //System.err.println("NASIS_copyHorizon: cec7 or ecec fields not populated ");
        //if ((this.cec7[idx] < 0.0) && (this.ecec[idx] < 0.0)) throw new FieldNotPopulated("cec7 and ecec");

        this.om[idx] = getValueFromRS(rs, "om");
        this.caco3[idx] = getValueFromRS(rs, "caco3");
        this.ph1to1h2o[idx] = getValueFromRS(rs, "ph1to1h2o");
        this.ph01mcacl2[idx] = getValueFromRS(rs, "ph01mcacl2");
        this.lep[idx] = getValueFromRS(rs, "lep");

    }
    
    protected void copyTexture(ResultSet rs, int idx) {
        this.texture[idx] = "";
        try {
            rs.next();
            String temp = rs.getString("texture");
            temp = (temp == null) ? "" : temp;
            temp = temp.replace("-", "_");
            this.texture[idx] = temp;

            String stratifiedString = rs.getString("stratextsflag");
            stratified[idx] = stratifiedString != null ? (stratifiedString.trim().equalsIgnoreCase("yes")) : false;
            String rvString = rs.getString("rvindicator");
            rv[idx] = rvString != null ? (rvString.trim().equalsIgnoreCase("yes")) : false;

        } catch (SQLException e) {
        }
    }

    /********************************************************************** wjr
     * @param rs
     * @param idx */
    protected void copyFrags(ResultSet rs, int idx) {
        this.fragvol[idx] = 0.0;
        try {
            while (rs.next()) {
                double tempFragVol = getValueFromRS(rs, "fragvol");
                this.fragvol[idx] += (Double.isNaN(tempFragVol)) ? 0.0 : tempFragVol;
            }
        } catch (SQLException e) {
        }
    }
    
    protected String getStringValueFromRS(ResultSet rs, String fieldName) {
        try {
            return rs.getString(fieldName);
        } catch (SQLException e) {
            return null;
        }
    }

    
    /**
     *
     * @param rs
     * @param fieldName
     * @return
     */
    protected int getIntergerValueFromRS(ResultSet rs, String fieldName) {
        try {
            return rs.getInt(fieldName);
        } catch (SQLException e) {
            return Integer.MIN_VALUE;
        }
    }

    /**
     *
     * @param rs
     * @param fieldName
     * @return
     */
    protected Date getDateValueFromRS(ResultSet rs, String fieldName) {
        try {
            return rs.getDate(fieldName);
        } catch (SQLException e) {
            return null;
        }
    }

    /**
     *
     * @param rs
     * @param fieldName
     * @return
     */
    protected double getValueFromRS(ResultSet rs, String fieldName) {
        return getValueFromRS(rs, fieldName, Double.NaN);
    }

    /**
     *
     * @param rs
     * @param fieldName
     * @param defaultValue
     * @return
     */
    protected double getValueFromRS(ResultSet rs, String fieldName, double defaultValue) {
        try {
            String rtnStr = rs.getString(fieldName + "_r");
            if (rtnStr == null) {
                throw new SQLException();
            }
            try {
                return Double.parseDouble(rtnStr);
            } catch (NumberFormatException g) {
                throw new SQLException();
            }
        } catch (SQLException e) {
            try {
                String rtn_l = rs.getString(fieldName + "_l");
                String rtn_h = rs.getString(fieldName + "_h");
                if (rtn_l == null || rtn_h == null) {
                    throw new SQLException();
                }
                try {
                    return (Double.parseDouble(rtn_l) + Double.parseDouble(rtn_h)) / 2;
                } catch (NumberFormatException h) {
                    throw new SQLException();
                }
            } catch (SQLException f) {
                return defaultValue;
            }
        } catch (NullPointerException npe) {
            return defaultValue;
        }
    }

    /**
     *
     * @param resultSet
     * @param fieldName
     * @param defaultValue
     * @return
     */
    protected String getValueFromRS(ResultSet resultSet, String fieldName, String defaultValue) {
        try {
            String temp = resultSet.getString(fieldName);
            return temp != null ? temp : defaultValue;
        } catch (Exception e) {
            // todo: it looks like this catch clause is always activated
            return defaultValue;
        }
    }
    
    private void logSubstitutionMessage(Level severity,
            String dataElement, String header, Object newValue) {
        logSubstitutionMessage(severity, dataElement, header, newValue, -1);
    }

    private void logSubstitutionMessage(Level severity,
            String dataElement, String header, Object newValue, int layer) {
        String layerTag = "";
        if (layer > -1) {
            layerTag = " from layer " + (layer + 1);
        }
        String tempMessage = dataElement + " ("
                + header + ") is missing" + layerTag + ".  Null value was substituted with '" + newValue.toString() + "'.";
        LOG.log(severity, tempMessage);

    }
    
    private void logMessage(Level severity, String message) {
        LOG.log(severity, message);
    }
        
    private void logEstimatedMessage(Level severity,
        String dataElement, String header, Object newValue) {
        logEstimatedMessage(severity, dataElement, header, newValue, -1);
    }

    private void logEstimatedMessage(Level severity,
            String dataElement, String header, Object newValue, int layer) {
        String layerTag = "";
        if (layer > -1) {
            layerTag = " from layer " + (layer + 1);
        }
        String tempMessage = dataElement
                + " (" + header + ") is missing" + layerTag
                + ".  Value was estimated to '" + newValue.toString() + "'.";
        LOG.log(severity, tempMessage);
    }
}