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

import csip.Config;
import csip.ModelDataService;
import csip.annotations.Description;
import csip.annotations.Name;
import csip.annotations.Resource;
import csip.api.server.ServiceException;
import csip.utils.JSONUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.ws.rs.Path;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import soils.Component;
import soils.Horizon;
import soils.MapUnit;
import soils.db.SOILS_DATA;
import soils.db.SOILS_DB_Factory;
import utils.EvalResult;
import java.util.LinkedHashMap;
import static soils.db.DBResources.SDM;
/**
 *
 * @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
 */
@Name("AggSoilParams")
@Description("Returns component horizon data aggrigated by horizon layers within upper, lower and sub root zones")
@Path("d/aggsoilparams/1.0")
@Resource(from = soils.db.DBResources.class)
public class V1_0 extends ModelDataService {

  static final String KEY_COKEY = "cokey";
  static final String KEY_URZ = "urz_bd";
  static final String KEY_LRZ = "lrz_bd";
  static final double DEFAULT_MISSING_VALUE = Double.NaN;

  protected Component comp = null;
  protected MapUnit mapUnit = null;
  protected String cokey;
  protected ArrayList<HorizonDataExt> extData = new ArrayList<>();
  protected int urz_bd;
  protected int lrz_bd;

  @Override
  protected void preProcess() throws Exception {
    cokey = parameter().getString(KEY_COKEY);
    urz_bd = parameter().getInt(KEY_URZ, 30);
    lrz_bd = parameter().getInt(KEY_LRZ, 90);
  }

  @Override
  protected Map<String, Object> getConfigInfo() {
    return new LinkedHashMap<String, Object>() {
      {
        put("soils.gis.database.source", resources().getResolved("soils.gis.database.source"));
        put(SDM, resources().getResolved(SDM));
        put("fpp.version", "asp 1.0");
      }
    };
  }

  @Override
  protected void doProcess() throws Exception {
    comp = new Component();
    comp.cokey(cokey);

    //  Get the WEPP filtered SOILS data for the cokey provided.
    try (SOILS_DATA soilsDb = SOILS_DB_Factory.createEngine(getClass(), LOG, Config.getString("soils.gis.database.source"))) {
      if (soilsDb.validateComponent(Integer.parseInt(comp.cokey()))) {
        if (null == (mapUnit = soilsDb.findWEPPDataByCokey(comp))) {
          throw new ServiceException("Could not retrieve the proper SDM soils data for this cokey");
        }
        retrieveExtraHorizonData(mapUnit.components().get(cokey), soilsDb.getConnection());
      } else {
        throw new ServiceException("The cokey value provided, " + comp.cokey() + ", does not exist in the SDM database.  Cannot continue.");
      }
    }
  }

  @Override
  protected void postProcess() throws Exception {
    ZoneData urz = new ZoneData(0.0, urz_bd);
    ZoneData lrz = new ZoneData(urz_bd, lrz_bd);
    ZoneData srz = new ZoneData();
    srz.top = lrz_bd;
    Zones zones = new Zones();
    zones.currentZone = urz;
    zones.nextZone = lrz;
    zones.lastZone = srz;
    boolean foundRestrictingHorizon = false;

    Horizon rHorizon = mapUnit.components().get(cokey).restrictingHorizon();
    for (HorizonDataExt hzData : extData) {
      // Did we reach a restricting horizon?
      if (foundRestrictingHorizon) {
        break;
      }

      if ((null != rHorizon) && (rHorizon.chkey() == hzData.chkey)) {
        foundRestrictingHorizon = true;
      }
      fillRootZoneData(zones, hzData, 0.0);
    }
    srz.bottom = zones.lastDepth;
    srz.thickness = srz.bottom - srz.top;

    //  Finish weighting the values
    urz.weightValues();
    lrz.weightValues();
    srz.weightValues();

    results().put("hydro_group", mapUnit.components().get(cokey).hydgrp());
    results().put("drainage_class", mapUnit.components().get(cokey).drainagecl());
    results().put("urz_aggr_data", urz.getJSONDataArray("urz"));
    results().put("lrz_aggr_data", lrz.getJSONDataArray("lrz"));
    results().put("srz_aggr_data", srz.getJSONDataArray("srz"));

  }

  void fillRootZoneData(Zones zones, HorizonDataExt hzData, double thicknessDiff) {
    double thickness;  // multiplier
    double thicknessLeftover = 0.0;
    boolean adjusted = false;

    zones.lastDepth = hzData.bottom;

    //Adjust multiplier
    if (thicknessDiff == 0.0) {
      if (hzData.bottom > zones.currentZone.bottom) {
        thickness = zones.currentZone.bottom - hzData.top;
        thicknessLeftover = hzData.bottom - zones.currentZone.bottom;
        adjusted = true;
      } else {
        thickness = hzData.thickness;
      }
    } else {
      if (thicknessDiff > zones.currentZone.thickness) {
        adjusted = true;
        thickness = zones.currentZone.thickness;
        thicknessLeftover = thicknessDiff - thickness;
      }
      thickness = thicknessDiff;
    }

    //  Set values
    zones.currentZone.setValue("om", hzData.om, thickness);
    zones.currentZone.setValue("ksat", hzData.ksat, thickness);
    zones.currentZone.setValue("blkdens", hzData.blkdens, thickness);
    zones.currentZone.setValue("wiltpt", hzData.wiltpt, thickness);
    zones.currentZone.setValue("fldcp", hzData.fldcp, thickness);
    zones.currentZone.setValue("clay", hzData.clay, thickness);
    zones.currentZone.setValue("sand", hzData.sand, thickness);
    zones.currentZone.setValue("silt", hzData.silt, thickness);
    zones.currentZone.setValue("satwc", hzData.satwc, thickness);
    zones.currentZone.setValue("awc", hzData.awc, thickness);
    zones.currentZone.setValue("ec", hzData.ec, thickness);
    zones.currentZone.setValue("caco3", hzData.caco3, thickness);
    zones.currentZone.setValue("gypsum", hzData.gypsum, thickness);
    zones.currentZone.setValue("sar", hzData.sar, thickness);
    zones.currentZone.setValue("pbray1", hzData.pbray1, thickness);
    zones.currentZone.setValue("ph2osoluble", hzData.ph2osoluble, thickness);
    zones.currentZone.setValue("ptotal", hzData.ptotal, thickness);
    zones.currentZone.setValue("ph", hzData.ph, thickness);

    // Do we have extra left over?
    if (adjusted) {
      zones.currentZone = zones.nextZone;
      zones.nextZone = zones.lastZone;
      zones.lastZone = null;
      fillRootZoneData(zones, hzData, thicknessLeftover);
    }
  }

  void retrieveExtraHorizonData(Component comp, Connection conn) throws SQLException, ServiceException {
    String query
        = "SELECT chkey, gypsum_r, sar_r, pbray1_r, ph2osoluble_r, ptotal_r, "
        + " wsatiated_r, awc_r "
        + " FROM chorizon WHERE cokey=" + Integer.parseInt(comp.cokey())
        + " ORDER BY hzdept_r;";

    try (Statement stmnt = conn.createStatement()) {
      ResultSet results = stmnt.executeQuery(query);
      while (results.next()) {
        HorizonDataExt tData = new HorizonDataExt();

        tData.readSQL(results, comp);
        extData.add(tData);
      }

    }
  }

  protected double fixValue(double origVal, double newVal, double thickness) {
    double result = origVal;

    if (Double.isNaN(origVal)) {
      if (!Double.isNaN(newVal)) {
        result = newVal * thickness;
      }
    } else {
      if (!Double.isNaN(newVal)) {
        result += newVal * thickness;
      }
    }
    return result;
  }

  public class Zones {

    ZoneData currentZone;
    ZoneData nextZone;
    ZoneData lastZone;
    double lastDepth;
  }

  public class ZoneData {

    public class ValueData {

      double value = Double.NaN;
      double thickness = 0.0;
    }

    Map<String, ValueData> values = new HashMap<>();

    boolean weighted = false;
    double thickness = Double.NaN;
    double top = Double.NaN;
    double bottom = Double.NaN;

    ZoneData(double _top, double _bottom) {
      thickness = _bottom - _top;
      bottom = _bottom;
      top = _top;
      initValues();
    }

    ZoneData() {
      top = 0;
      bottom = Double.MAX_VALUE;
      thickness = Double.NaN;
      initValues();
    }

    private void initValues() {
      values.put("om", new ValueData());
      values.put("ksat", new ValueData());
      values.put("blkdens", new ValueData());
      values.put("wiltpt", new ValueData());
      values.put("fldcp", new ValueData());
      values.put("clay", new ValueData());
      values.put("sand", new ValueData());
      values.put("silt", new ValueData());
      values.put("satwc", new ValueData());
      values.put("awc", new ValueData());
      values.put("ec", new ValueData());
      values.put("caco3", new ValueData());
      values.put("gypsum", new ValueData());
      values.put("sar", new ValueData());
      values.put("pbray1", new ValueData());
      values.put("ph2osoluble", new ValueData());
      values.put("ptotal", new ValueData());
      values.put("ph", new ValueData());
    }

    public void setValue(String name, double value, double thickness) {
      double result = values.get(name).value;

      if (Double.isNaN(result)) {
        if (!Double.isNaN(value)) {
          values.get(name).value = value * thickness;
          values.get(name).thickness = thickness;
        }
      } else {
        if (!Double.isNaN(value)) {
          values.get(name).value += value * thickness;
          values.get(name).thickness += thickness;
        }
      }

    }

    public void weightValues() throws ServiceException {
      if (Double.isNaN(thickness)) {
        throw new ServiceException("Cannot adjust weights, no thickness was calculated.");
      }

      if (!weighted) {
        for (ValueData vData : values.values()) {
          vData.value /= ((vData.thickness == 0.0) ? 1 : vData.thickness);
        }
        weighted = true;
      }
    }

    public JSONArray getJSONDataArray(String prefix) throws JSONException {
      JSONArray result = new JSONArray();
      SortedSet<String> names = new TreeSet<>();
      names.addAll(values.keySet());
      result.put(JSONUtils.data("top", top));
      result.put(JSONUtils.data("bottom", bottom));
      result.put(JSONUtils.data("thickness", thickness));

      for (String vName : names) {
        result.put(JSONUtils.data(prefix + "_" + vName, EvalResult.writeDouble(values.get(vName).value, "%.2f")));
      }

      return result;
    }
  }

  public class HorizonDataExt {

    String chkey;
    double thickness;
    double top;
    double bottom;
    double om;
    double ksat;
    double blkdens;
    double wiltpt;
    double fldcp;
    double clay;
    double sand;
    double silt;
    double satwc;
    double awc;
    double ec;
    double caco3;
    double gypsum;
    double sar;
    double pbray1;
    double ph2osoluble;
    double ptotal;
    double ph;

    void readSQL(ResultSet results, Component comp) throws SQLException {
      chkey = results.getString("chkey");
      Horizon hz = comp.horizons().get(chkey);

      thickness = hz.hzthk_r();
      top = hz.hzdept_r();
      bottom = hz.hzdepb_r();
      om = hz.om_r();
      ksat = hz.ksat_r();
      blkdens = hz.dbthirdbar_r();
      wiltpt = hz.wfifteenbar_r();
      fldcp = hz.wthirdbar_r();
      clay = hz.claytotal_r();
      sand = hz.sandtotal_r();
      silt = hz.silttotal_r();
      ec = hz.ec_r();
      caco3 = hz.caco3_r();
      ph = hz.ph1to1h2o_r();

      satwc = getDouble(results, "wsatiated_r", DEFAULT_MISSING_VALUE);
      awc = getDouble(results, "awc_r", DEFAULT_MISSING_VALUE);
      gypsum = getDouble(results, "gypsum_r", DEFAULT_MISSING_VALUE);
      sar = getDouble(results, "sar_r", DEFAULT_MISSING_VALUE);
      pbray1 = getDouble(results, "pbray1_r", DEFAULT_MISSING_VALUE);
      ph2osoluble = getDouble(results, "ph2osoluble_r", DEFAULT_MISSING_VALUE);
      ptotal = getDouble(results, "ptotal_r", DEFAULT_MISSING_VALUE);

    }

    protected double getDouble(ResultSet results, String name, double def) throws SQLException {
      double value = EvalResult.getDouble(results, name);

      return (EvalResult.testDefaultDouble(value) ? def : value);
    }
  }

}