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);
}
}
}