V1_0.java [src/java/d/bamert_01/edit_soils_properties] Revision:   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.bamert_01.edit_soils_properties;

import com.jayway.jsonpath.ReadContext;
import csip.Config;
import csip.ModelDataService;
import csip.ServiceException;
import csip.SessionLogger;
import csip.annotations.Description;
import csip.annotations.Name;
import csip.annotations.Resource;
import csip.annotations.VersionInfo;
import csip.utils.JSONUtils;
import csip.utils.Parallel;
import csip.utils.Parallel.Run;
import static db.DBResources.GIS_DB;
import edit.EditConnection;
import edit.EditQueries;
import gisobjects.GISObject;
import static gisobjects.GISObject.DEFAULT_ASSUMED_SRID;
import gisobjects.GISObjectException;
import gisobjects.GISObjectFactory;
import gisobjects.db.GISEngine;
import gisobjects.db.GISEngineFactory;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.ws.rs.Path;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import soils.Coecoclass;
import soils.Component;
import soils.MapUnit;
import soils.SoilsData;
import static soils.db.DBResources.PROVIDER_TYPE;
import soils.db.SOILS_DATA;
import soils.db.SOILS_DB_Factory;
import soils.db.tables.TableCoecoclass;
import soils.db.tables.TableCoecoclassCalculations;
import soils.db.tables.TableComponent;
import soils.db.tables.TableComponentCalculations;
import soils.db.tables.TableHorizon;
import soils.db.tables.TableHorizonCalculations;
import soils.db.tables.TableMapUnit;
import soils.db.tables.TableMapUnitCalculations;
import soils.db.tables.TableTextureGroup;

/**
 *
 * @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
 */
@Name("BAMERT-01:  Get Soil Map Unit and Component Properties")
@Description("Get and return a payload of Soil Data Mart (SDM) soil properties "
    + "and Ecosystem Dynamics Interpretative Tool (EDIT) ecological site properties.")
@VersionInfo("1.0")
@Path("d/edit_soils_properties/1.0")
@Resource(from = soils.db.DBResources.class)
@Resource(from = db.DBResources.class)

public class V1_0 extends ModelDataService {

  static final String AOA_GEOMETRY = "aoa_geometry";

  JSONObject aoa_geometry;
  AoA aoa;

  @Override
  protected void preProcess() throws Exception {
    aoa_geometry = parameter().getParamJSON(AOA_GEOMETRY);

  }

  @Override
  protected void doProcess() throws Exception {
    try (GISEngine gisEngine = GISEngineFactory.createGISEngine(resources().getJDBC(GIS_DB));
        SOILS_DATA soilsDb = SOILS_DB_Factory.createEngine(V1_0.class, LOG, Config.getString("soils.gis.database.source"));) {
      GISObject aoaGeometry = GISObjectFactory.createGISObject(aoa_geometry, gisEngine);
      aoa = new AoA(soilsDb, aoaGeometry, LOG);
      aoa.findSoils();
    }
  }

  @Override
  protected void postProcess() throws Exception {
    results().put("aoa_acres", aoa.getArea());
    results().put("mapunits", aoa.toJSON());
  }

  public class AoA extends soils.AoA {

    private SortedMap<Double, Component> componentOrderList = new TreeMap<>(Collections.reverseOrder());

    public static final String BUFFER_SIZE_KEY = "buffer_size";
    static final double MINIMUM_PERCENTAGE_2 = 20.0;
    static final int ALLOWED_SRID = DEFAULT_ASSUMED_SRID;
    static final double DEFAULT_WEI = 134.0;
    static final double DEFAULT_POINT_BUFFER_SIZE = 113.5; // 133.5m buffer = 10 acres.

    protected String mupolygonkey = "";  //  This is held over from the original, in case we want to search by mupolygon in the future.
    protected GISEngine gisEngine = null;
    protected double bufferSize = DEFAULT_POINT_BUFFER_SIZE;

    /**
     *
     * @param soilsDb
     * @param aoa_geometry
     * @param Log
     * @throws GISObjectException
     * @throws SQLException
     * @throws JSONException
     * @throws IOException
     * @throws ServiceException
     */
    public AoA(SOILS_DATA soilsDb, GISObject aoa_geometry, SessionLogger Log) throws GISObjectException, SQLException, JSONException, IOException, ServiceException {
      super(soilsDb, aoa_geometry, Log);

      if (ALLOWED_SRID != getSRID()) {
        throw new ServiceException("This service, currently, only supports an input CRS of WGS-84 (4326 SRID).  You requested service for a geometry that was SRID: " + getSRID());
      }

      shape.makeValid(GISObject.UsePurpose.all_purposes, GISObject.GISType.all_types);

      if (shape.getGeometry().getGeometryType() == "Point") {
        shape = shape.buffer(bufferSize);
      }

      area = shape.areaInAcres();

      if (area > 10000.0) {
        throw new ServiceException("Feature specified has an area greater than 10,000 acres.  Area is too large.");
      }
    }

    /**
     *
     * @throws SQLException
     * @throws ServiceException
     * @throws GISObjectException
     */
    public void findSoils() throws SQLException, ServiceException, GISObjectException, Exception {

      if (null != shape) {
        findIntersectedMapUnitsWithAreasAndShapes();
      } else {
        throw new ServiceException("No AoI location was speified.  Please specify an AoI location as a geometry.");
      }

      if (soilsDb.findAllBasicComponentHorizonFragTextureData(map_units, true)) {
        soilsDb.findEcoClassForMukeyList(map_units,
            " ((ecoclassid LIKE 'R%' AND ecoclasstypename='NRCS Rangeland Site') "
            + " OR (ecoclassid LIKE 'F%' AND ecoclasstypename='NRCS Forestland Site'))"
        );
        getEDITEcoclassNames();

      }
    }

    private void queryEdit(Coecoclass coEcoClass, EditConnection editConn) throws Exception {
      String ecId = coEcoClass.ecoclassid();
      String edit_es_name = EditQueries.getEcoClassNameFor(editConn.fetchEcoClassList(ecId), ecId);
      if ((null != edit_es_name) && (!edit_es_name.isEmpty())) {
        coEcoClass.ecoclassname(edit_es_name);
        coEcoClass.setEDITPath("https://edit.jornada.nmsu.edu/catalogs/esd/" + ecId.substring(1, 5) + "/" + ecId);
      }
    }

    private void getEDITEcoclassNames() throws ServiceException, Exception {
      if (null != map_units) {
        ArrayList<Run> runs = new ArrayList<>();
        EditConnection editConn = new EditConnection(LOG);
        for (MapUnit mapUnit : map_units.values()) {
          for (Component component : mapUnit.components().values()) {
            for (Coecoclass coEcoClass : component.ecoClasses().values()) {
              String ecId = coEcoClass.ecoclassid();
              if (ecId.length() == 11 && ((ecId.charAt(0) == 'R' || ecId.charAt(0) == 'F'))) {
                runs.add(() -> queryEdit(coEcoClass, editConn));
              }
            }
          }
        }
        if (runs.size() > 0) {
          Parallel.run(runs);
        }
      } else {
        throw new ServiceException("Cannot find ecoclass names from EDIT with an empty mapunit list.");
      }
    }

    /**
     *
     * @return
     */
    public final int getSRID() {
      return shape.getGeometry().getSRID();
    }

    /**
     *
     * @return @throws JSONException
     */
    public JSONArray toJSON() throws JSONException, ServiceException, IOException {
      JSONArray mapunitData = new JSONArray();

      for (MapUnit mapUnit : map_units.values()) {
        if (!mapUnit.isExcluded()) {
          JSONArray mapUnitArray = new JSONArray();

          ArrayList<GISObject> intersections = mapUnit.getIntersectionPolygons();

          mapUnit.setOutputColumnOrdering(new ArrayList<>(Arrays.asList(
              TableMapUnit.AREASYMBOL_NAME, TableMapUnit.AREA_NAME, TableMapUnit.MUKEY, TableMapUnit.MUNAME,
              TableMapUnit.MUSYM, TableMapUnit.MUACRES, TableMapUnitCalculations.AREA_NAME, TableMapUnitCalculations.AREA_PCT
          )));
          mapUnit.mapUnitJSON(mapUnitArray);
          JSONArray intersectionArray = new JSONArray();
          for (GISObject intersect : intersections) {
            intersectionArray.put(intersect.toJSON());
          }
          mapUnitArray.put(JSONUtils.data("mapunit_intersections", intersectionArray));

          JSONArray componentArray = new JSONArray();
          for (Component component : mapUnit.components().values()) {
            if (!component.isExcluded()) {
              JSONArray componentObject;

              component.setOutputColumnOrdering(new ArrayList<>(Arrays.asList(
                  TableComponent.COKEY, TableComponent.COMPNAME, TableComponent.COMPPCT_R_NAME,
                  TableComponent.MAJ_COMP_FLAG, TableComponentCalculations.AREA_PCT_NAME, TableComponentCalculations.COMP_AREA_NAME
              )));

              component.setHorizonOutputColumnOrdering(new ArrayList<>(Arrays.asList(TableHorizon.CHKEY_NAME, TableHorizonCalculations.DEPT_R_IN,
                  TableHorizonCalculations.DEPB_R_IN)));
              component.setTextureGroupOutputColumnOrdering(new ArrayList<>(Arrays.asList(TableTextureGroup.DESCRIPTION)));

              componentObject = component.toJSON(false);

              JSONArray ecoClassArray = new JSONArray();
              for (Coecoclass ecoClass : component.ecoClasses().values()) {
                ecoClass.setOutputColumns(new ArrayList<>(Arrays.asList(TableCoecoclass.ECOCLASSID,
                    TableCoecoclass.ECOCLASSNAME, TableCoecoclassCalculations.ES_EDIT_URL)));
                ecoClassArray.put(ecoClass.toJSON());
              }

              componentObject.put(JSONUtils.data("EcoClassList", ecoClassArray));
              componentArray.put(componentObject);

            }
          }

          if (componentArray.length() > 0) {
            mapUnitArray.put(JSONUtils.data(SoilsData.MAPUNIT_COMPONENT_LIST_NAME, componentArray));
            mapunitData.put(mapUnitArray);
          }

        }
      }

      return mapunitData;
    }
  }
}