V1_0.java [src/java/m/rv/fisprodesd] 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-2022, 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 m.rv.fisprodesd;

import com.jayway.jsonpath.ReadContext;
import gisobjects.db.GISEngine;
import gisobjects.GISObject;
import gisobjects.GISObjectFactory;
import gisobjects.vector.GIS_FeatureCollection;
import csip.ModelDataService;
import csip.api.server.ServiceException;
import csip.SessionLogger;
import csip.annotations.Polling;
import csip.annotations.Resource;
import csip.utils.JSONUtils;
import java.io.IOException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import javax.ws.rs.Path;
import csip.annotations.Description;
import csip.annotations.Gzip;
import csip.annotations.Name;
import csip.annotations.VersionInfo;
import csip.utils.Numeric;
import csip.utils.Parallel;
import edit.EditQueries;
import edit.EditConnection;
import gisobjects.db.GISEngineFactory;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import java.util.Map;
import soils.Coecoclass;
import soils.Component;
import soils.MapUnit;
import soils.db.SOILS_DATA;
import soils.db.SOILS_DB_Factory;
import java.util.Arrays;
import java.util.List;
import java.util.TreeMap;
import m.rv.ApplicationResources;
import static m.rv.ApplicationResources.CRDB;
import static m.rv.ApplicationResources.SOILS_SOURCE;
import soils.utils.EvalResult;

/**
 * Create Forage Inventory Site and Get Ecological Site Estimated Production
 *
 * @version 1.0
 * @author odavid
 */
@Name("Create Forage Inventory Site and Get Ecological Site Estimated "
    + "Production (FISProdESD)")
@Description("This service creates one or more forage inventory sites (FISs) "
    + "per requested method: (1) one area of analysis (AoA) extent FIS, (2) "
    + "one or more AoA x soil mapunit polygon FISs, and (3) one or more user "
    + "defined FISs. The service then derives a set of soil components from "
    + "SSURGO for the AoA (usually a fenced grazing unit) and matches the "
    + "ecological site (ESD) identifier to the identifier in EDIT to get "
    + "estimated forage production for the plant communities representing "
    + "the ecological states of the ESD. This service applies to the NRCS "
    + "land uses of range, forest, protected, other rural land, and "
    + "associated agricultural land.")
@Path("m/rv/fisprodesd/1.0")
@VersionInfo("$Id$")
@Polling(first = 10000, next = 2000)
@Resource(from = ApplicationResources.class)
@Resource(from = soils.db.DBResources.class)
@Gzip
public class V1_0 extends ModelDataService {

  int aoaId;
  AoA aoa;
  JSONObject aoaGeometry;
  int fisMethod;
  JSONObject userFisGeometry;

  List<FIS> fisList = new ArrayList<>();
  GISObject aoaGisGeometry;


  @Override
  protected void preProcess() throws ServiceException {
    aoaId = parameter().getInt("AoAId", 0);
    aoaGeometry = parameter().getParam("aoa_geometry");
    fisMethod = parameter().getInt("fis_method");
    userFisGeometry = parameter().getParam("fis_geometry", null);

    if (!Arrays.asList(1, 2, 3).contains(fisMethod))
      throw new ServiceException("AoA " + aoaId + " has invalid FIS method value of "
          + fisMethod + ". Valid values are: 1, 2 and 3.");

    if ((userFisGeometry == null) && (fisMethod == 3))
      throw new ServiceException("fis_method '3' requires fis_geometry!");

  }


  @Override
  protected void doProcess() throws Exception {
    try (SOILS_DATA soilsDb = SOILS_DB_Factory.createEngine(getClass(), LOG, SOILS_SOURCE); Connection crdb = resources().getJDBC(CRDB); GISEngine gisEngine = GISEngineFactory.createGISEngine(crdb)) {
      aoaGisGeometry = GISObjectFactory.createGISObject(aoaGeometry, gisEngine);
      createFis(soilsDb, gisEngine, aoaGisGeometry);
      createEcoClasses();
    }
  }


  @Override
  protected void postProcess() throws Exception {
    try {
      results().put("AoAId", aoaId, "Area of Analysis Identifier");
      results().put("fis_type", fisMethod == 1 ? 1 : 2, "Forage Inventory Site Type");

      JSONArray fisArr = new JSONArray();
      for (FIS fis : fisList) {
        JSONArray indFISArr = new JSONArray()
            .put(JSONUtils.dataDesc("fis_id", fis.getFisId(), "Forage Inventory Site Identifier"))
            .put(JSONUtils.dataUnitDesc("fis_area", Numeric.round(fis.getFisArea(), 2), "acre", "Forage Inventory Site Area in Acres"));
        if (fis.getFisGeometry() != null)
          indFISArr.put(JSONUtils.dataDesc("fis_geometry", fis.getFisGeometry().toJSON(), "Forage Inventory Site Geometry"));

        JSONArray ecoArr = new JSONArray();

        // EditQueries use
        Map<String, EcoClass> ecm = fis.getEcoclassMap();
        for (EcoClass ec : ecm.values()) {
          JSONArray indESArr = gras_fisprodesd(ec.getEcoclassId(),
              ec.getFisMuCompPctfis(), ec.getMaxRsprod(), ec.getEcoclassName(), LOG);
          ecoArr.put(indESArr);
        }
        indFISArr.put(JSONUtils.data("Ecological Site List", ecoArr));
        fisArr.put(indFISArr);
      }
      results().put("FIS List", fisArr);
    } catch (JSONException | IOException ex) {
      throw new ServiceException(ex);
    }
  }


  /////
  /**
   * Get Ecological Site Estimated Production (gras_fisprodesd) from
   * EditQueries.
   *
   * @param es_id the ecological site id
   * @param es_pctfis
   * @param es_rsprod
   * @param es_name
   * @param log
   * @return the Ecological Site Estimated Production info
   * @throws Exception
   */
  JSONArray gras_fisprodesd(String ecId_, double es_pctfis,
      int es_rsprod, String es_name, SessionLogger log) throws Exception {

    EditConnection editConn = new EditConnection(log);

    // get the synonym if there is one, if not, ecId == ecId_.
    final String ecId = EditQueries.getSynonymFor(editConn.fetchAllSynonyms(), ecId_);

    String edit_es_name = EditQueries.getEcoClassNameFor(editConn.fetchEcoClassList(ecId), ecId);
    boolean useEdit = edit_es_name != null;

    JSONArray statesList = new JSONArray();

    ReadContext[] res = {null, null, null};
    if (useEdit) {
      log.info("Using EDIT for es_id: " + ecId);

      Parallel.run(
          () -> {
            res[0] = editConn.fetchEcoSystemStates(ecId);
          },
          () -> {
            res[1] = editConn.fetchAnnualProd(ecId);
          },
          () -> {
            res[2] = editConn.fetchPlantComposition(ecId);
          }
      );
      ReadContext states = res[0];
      ReadContext pcTable = res[1];

      List<Map<String, Object>> ecoSystStatesNarr = EditQueries.getEcoSystemStatesNarratives(states);
      for (int st = 0; st < ecoSystStatesNarr.size(); st++) {
        Map<String, Object> stateNarr = ecoSystStatesNarr.get(st);
        JSONArray stList = new JSONArray()
            .put(JSONUtils.dataDesc("state_id", st + 1, "States Identifier"))
            .put(JSONUtils.dataDesc("state_name", stateNarr.get("name"), "States Name"))
            .put(JSONUtils.dataDesc("state_description", stateNarr.get("description"), "States Description"));

        JSONArray pcsList = new JSONArray();
        stList.put(JSONUtils.data("PlantCommunity List", pcsList));

        List<Map<String, Object>> plantComm = EditQueries.getPlantCommunityNarrativesByState(states, st + 1);
        for (int pc = 0; pc < plantComm.size(); pc++) {
          Map<String, Object> plantCommNarr = plantComm.get(pc);
          JSONArray pcList = new JSONArray()
              .put(JSONUtils.dataDesc("plant_community_id", pc + 1, "Plant Community Identifier"))
              .put(JSONUtils.dataDesc("plant_community_name", plantCommNarr.get("name"), "Plant Community  Name"))
              .put(JSONUtils.dataDesc("plant_community_description", plantCommNarr.get("description"), "Plant Community Description"));
          List<List<Map<String, Object>>> images = EditQueries.getPlantCommunityImagesByPlantCommunity(states, st + 1, pc + 1);
          if (!images.isEmpty()) {
            JSONArray imsList = new JSONArray();
            pcList.put(JSONUtils.data("Image List", imsList));
            for (int im = 0; im < images.get(0).size(); im++) {
              Map<String, Object> image = images.get(0).get(im);
              JSONArray imList = new JSONArray()
                  .put(JSONUtils.dataDesc("image_id", im + 1, "Image Identifier"))
                  .put(JSONUtils.dataDesc("image_url", image.get("path"), "Image URL"))
                  .put(JSONUtils.dataDesc("image_caption", image.get("caption"), "Image Caption"))
                  .put(JSONUtils.dataDesc("image_orientation", image.get("orientation"), "Image Orientation"));

              imsList.put(imList);
            }
          }

          JSONArray apsList = new JSONArray();
          pcList.put(JSONUtils.data("AnnualProduction", apsList));
          List<List<Map<String, Object>>> prod = EditQueries.getAnnualProd(pcTable, 1, st + 1, pc + 1);
          if (!prod.isEmpty()) {
            for (Map<String, Object> p : prod.get(0)) {
              JSONArray apList = new JSONArray()
                  .put(JSONUtils.dataDesc("plant_type", p.get("plantType"), "Plant Type Label"))
                  .put(JSONUtils.dataUnitDesc("low_prod", p.get("low"), "lb/acre/yr", "Low value"))
                  .put(JSONUtils.dataUnitDesc("rv_prod", p.get("rv"), "lb/acre/yr", "Representative value"))
                  .put(JSONUtils.dataUnitDesc("high_prod", p.get("high"), "lb/acre/yr", "High value"));

              apsList.put(apList);
            }
          }
          pcsList.put(pcList);
        }
        statesList.put(stList);
      }
    } else {
      log.info("Using SDM for es_id: " + ecId);
    }

    // one LU at the moment.
    JSONArray luList = new JSONArray().put(JSONUtils.data("States List", statesList));

    JSONArray lusList = new JSONArray();
    lusList.put(luList);

    JSONArray esList = new JSONArray()
        .put(JSONUtils.dataDesc("es_id", ecId, "Ecological Site Identifier"))
        .put(JSONUtils.dataDesc("es_name",
            useEdit ? edit_es_name : es_name,
            "Ecological Site Name (" + (useEdit ? "EDIT" : "SDM") + ")"));

    if (useEdit) {
      esList.put(JSONUtils.dataDesc("es_edit_url", EditQueries.getSiteDescrUrl(ecId), "Ecological Site Description Url"));

      ReadContext plantComp = res[2];
      Map<Integer, List<Map<String, Object>>> groups = new TreeMap<>();
      List<List<Map<String, Object>>> cg = EditQueries.getCompositionGroups(plantComp);
      for (List<Map<String, Object>> comps : cg) {
        for (Map<String, Object> comp : comps) {
          add(comp, groups);
        }
      }

      // Groups
      JSONArray jgroups = new JSONArray();
      for (List<Map<String, Object>> gr : groups.values()) {
        JSONArray group = new JSONArray();
        jgroups.put(group);
        for (Map<String, Object> o : gr) {
          JSONArray sym = new JSONArray()
              .put(JSONUtils.dataDesc("symbol", o.get("symbol"), "Plant Symbol"))
              .put(JSONUtils.dataDesc("common_name", o.get("commonName"), "Plant Common Name"))
              .put(JSONUtils.dataDesc("scientific_name", o.get("scientificName"), "Plant Scientific Name"))
              .put(JSONUtils.dataDesc("production_low", o.get("productionLow"), "Prod Low"))
              .put(JSONUtils.dataDesc("production_high", o.get("productionHigh"), "Prod High"));
          group.put(sym);
        }
      }
      esList.put(JSONUtils.data("Groups", jgroups));
    }

    esList.put(JSONUtils.dataDesc("es_pctfis", Numeric.round(es_pctfis, 2), "Percent of Forage Inventory Site"))
        .put(JSONUtils.dataDesc("es_rsprod", es_rsprod, "Estimated Forage Production"))
        .put(JSONUtils.data("Landuse List", lusList));

    return esList;
  }


  private void add(Map<String, Object> o, Map<Integer, List<Map<String, Object>>> groups)
      throws JSONException {
    Integer index = (Integer) o.get("group");
    List group = groups.get(index);
    if (group == null)
      groups.put(index, group = new ArrayList());

    group.add(o);
  }


  private void createFis(SOILS_DATA soilsDb, GISEngine gisEngine, GISObject aoaGISGeo) throws Exception {

    int row = 0;

    switch (fisMethod) {
      case 1:
        aoa = new AoA(soilsDb, aoaGISGeo, LOG);
        aoa.findIntersectedMapUnitsWithAreas();
        List<Mu> fisMuList = new ArrayList<>();
        for (MapUnit mapUnit : aoa.getMapUnits().values()) {
          aoa.findComponentsByMapunit(mapUnit.mukey());
          fisMuList.add(new Mu(++row, mapUnit.mukey(), mapUnit.area()));
        }
        fisList.add(new FIS(1, aoaGisGeometry, aoa.getArea(), fisMuList));
        break;

      case 2:
        aoa = new AoA(soilsDb, aoaGISGeo, LOG);
        aoa.findIntersectedMapUnitsWithAreasAndShapes();
        for (MapUnit mu : aoa.getMapUnits().values()) {
          aoa.findComponentsByMapunit(mu.mukey());
          for (int i = 0; i < mu.getIntersectionShapes().size(); i++) {
            fisList.add(new FIS(++row, mu.getIntersectionShapes().get(i),
                mu.getIntersectionShapes().get(i).areaInAcres(), mu.mukey()));
          }
        }
        break;

      case 3:
        GIS_FeatureCollection fc = (GIS_FeatureCollection) GISObjectFactory.createGISObject(userFisGeometry, gisEngine);
        for (int i = 0; i < fc.getFeatureCount(); i++) {
          GISObject userFisGISGeometry = fc.getGeometry(i);
          double userFisGISGeometryArea = userFisGISGeometry.areaInAcres();
          int fis_id = Integer.parseInt(fc.getFeatureAttribute(i, "fis_id"));

          aoa = new AoA(soilsDb, userFisGISGeometry, LOG);
          aoa.findIntersectedMapUnitsWithAreas();
          fisMuList = new ArrayList<>();
          for (MapUnit mapUnit : aoa.getMapUnits().values()) {
            aoa.findComponentsByMapunit(mapUnit.mukey());
            fisMuList.add(new Mu(++row, mapUnit.mukey(), mapUnit.area()));
          }
          fisList.add(new FIS(fis_id, userFisGISGeometry, userFisGISGeometryArea, fisMuList));
        }
        break;
    }
    aoa.findEcoClasses();
  }


  private void createEcoClasses() throws ServiceException {
    for (FIS fis : fisList) {
      Map<String, EcoClass> eco = new HashMap();
      switch (fisMethod) {
        case 1:
        case 3:
          //For fisMethod 1 and 3
          for (Mu mu : fis.getFisMuList()) {
            Map<String, MapUnit> mapUnits = aoa.getMapUnits();
            for (MapUnit mapUnit : mapUnits.values()) {
              if (mapUnit.mukey().equals(mu.mukey)) {
                for (Component component : mapUnit.components().values()) {
                  int rsprod_r = component.rsprod_r();
                  double fisMuCompPctfis = -1;
                  switch (fisMethod) {
                    case 1:
                      fisMuCompPctfis = component.comppct_r() * mu.getFisMuArea() / aoa.getArea();
                      break;
                    case 3:
                      fisMuCompPctfis = component.comppct_r() * mu.getFisMuArea() / fis.getFisArea();
                      break;
                  }
                  for (Coecoclass coEcoClass : component.ecoClasses().values()) {
                    String ecoclassid = coEcoClass.ecoclassid();
                    String ecoclassName = coEcoClass.ecoclassname();
                    String ecoclassType = coEcoClass.ecoclasstypename();
                    if ((ecoclassid.startsWith("R") && ecoclassType.equals("NRCS Rangeland Site"))
                        || (ecoclassid.startsWith("F") && ecoclassType.equals("NRCS Forestland Site"))) {
                      if (!eco.containsKey(ecoclassid)) {
                        if (!EvalResult.testDefaultInteger(rsprod_r))
                          eco.put(ecoclassid, new EcoClass(ecoclassid, ecoclassName, fisMuCompPctfis, rsprod_r));
                        else
                          eco.put(ecoclassid, new EcoClass(ecoclassid, ecoclassName, fisMuCompPctfis, -1));

                      } else {
                        EcoClass ecoClass = eco.get(ecoclassid);
                        ecoClass.addEsPctfis(fisMuCompPctfis);
                        if (!EvalResult.testDefaultInteger(rsprod_r))
                          ecoClass.addRsprod(rsprod_r);
                      }
                    }
                  }
                }
              }
            }
          }
          break;

        //For fisMethod 2
        case 2:
          Map<String, MapUnit> mapUnits = aoa.getMapUnits();
          for (MapUnit mapUnit : mapUnits.values()) {
            if (mapUnit.mukey().equals(fis.mukey)) {
              for (Component component : mapUnit.components().values()) {
                int rsprod_r = component.rsprod_r();
                double fisMuCompPctfis = component.comppct_r();
                for (Coecoclass coEcoClass : component.ecoClasses().values()) {
                  String ecoclassid = coEcoClass.ecoclassid();
                  String ecoclassName = coEcoClass.ecoclassname();
                  String ecoclassType = coEcoClass.ecoclasstypename();
                  if ((ecoclassid.startsWith("R") && ecoclassType.equals("NRCS Rangeland Site"))
                      || (ecoclassid.startsWith("F") && ecoclassType.equals("NRCS Forestland Site"))) {
                    if (!eco.containsKey(ecoclassid)) {
                      if (!EvalResult.testDefaultInteger(rsprod_r))
                        eco.put(ecoclassid, new EcoClass(ecoclassid, ecoclassName, fisMuCompPctfis, rsprod_r));
                      else
                        eco.put(ecoclassid, new EcoClass(ecoclassid, ecoclassName, fisMuCompPctfis, -1));
                    } else {
                      EcoClass ecoClass = eco.get(ecoclassid);
                      ecoClass.addEsPctfis(fisMuCompPctfis);
                      if (!EvalResult.testDefaultInteger(rsprod_r))
                        ecoClass.addRsprod(rsprod_r);
                    }
                  }
                }
              }
            }
          }
          break;
      }
      fis.setEcoclassMap(eco);
    }
  }

  static class AoA extends soils.AoA {

    AoA(SOILS_DATA soilsDb, GISObject aoaShape, SessionLogger Log) throws Exception {
      super(soilsDb, aoaShape, Log);
    }
  }

  static class FIS {

    int fisId;
    GISObject fisGeometry;
    double fisArea;
    List<Mu> fisMuList;
    String mukey;
    Map<String, EcoClass> ecoclassMap;

    //For method 1 and 3

    FIS(int id, GISObject geometry, double area, List<Mu> fisMuList) {
      fisId = id;
      fisGeometry = geometry;
      fisArea = area;
      this.fisMuList = fisMuList;
    }

    //For method 2

    FIS(int id, GISObject geometry, double area, String fisMuId) {
      fisId = id;
      fisGeometry = geometry;
      fisArea = area;
      mukey = fisMuId;
    }


    public int getFisId() {
      return fisId;
    }


    public GISObject getFisGeometry() {
      return fisGeometry;
    }


    public double getFisArea() {
      return fisArea;
    }


    public List<Mu> getFisMuList() {
      return fisMuList;
    }


    public String getMukey() {
      return mukey;
    }


    public Map<String, EcoClass> getEcoclassMap() {
      return ecoclassMap;
    }


    public void setEcoclassMap(Map<String, EcoClass> ecoclassMap) {
      this.ecoclassMap = ecoclassMap;
    }

  }

  static class Mu {

    int fisMuId;
    String mukey;
    double fisMuArea;


    Mu(int fisMuId, String mukey, double fisMuArea) {
      this.fisMuId = fisMuId;
      this.mukey = mukey;
      this.fisMuArea = fisMuArea;
    }


    public int getFisMuId() {
      return fisMuId;
    }


    public String getMukey() {
      return mukey;
    }


    public double getFisMuArea() {
      return fisMuArea;
    }

  }

  static class EcoClass {

    String ecoclassId;
    String ecoclassName;
    double fisMuCompPctfis;
    List<Integer> rsprodList = new ArrayList<>();


    EcoClass(String id, String name, double fisMuCompPctfis, Integer rsprod) {
      ecoclassId = id;
      ecoclassName = name;
      this.fisMuCompPctfis = fisMuCompPctfis;
      rsprodList.add(rsprod);
    }


    public String getEcoclassId() {
      return ecoclassId;
    }


    public String getEcoclassName() {
      return ecoclassName;
    }


    public double getFisMuCompPctfis() {
      return fisMuCompPctfis;
    }


    public List<Integer> getRsprod() {
      return rsprodList;
    }


    public void addEsPctfis(double value) {
      fisMuCompPctfis += value;
    }


    public void addRsprod(Integer value) {
      rsprodList.add(value);
    }


    public int getMaxRsprod() {
      return Collections.max(rsprodList);
    }

  }
}