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

import csip.ModelDataService;
import csip.api.server.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 edit.EditConnection;
import edit.EditQueries;
import edit.Utils;
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.List;
import javax.ws.rs.Path;
import m.rv.ApplicationResources;
import static m.rv.ApplicationResources.GIS_DB;
import static m.rv.ApplicationResources.SOILS_SOURCE;
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 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("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 = ApplicationResources.class)
@Resource(from = soils.db.DBResources.class)
public class V1_0 extends ModelDataService {

  static final String AOA_GEOMETRY = "aoa_geometry";


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

    try (GISEngine gisEngine = GISEngineFactory.createGISEngine(resources().getJDBC(GIS_DB)); SOILS_DATA soilsDb = SOILS_DB_Factory.createEngine(V1_0.class, LOG, SOILS_SOURCE)) {
      GISObject aoaGeometry = GISObjectFactory.createGISObject(aoa_geometry, gisEngine);
      AoA aoa = new AoA(soilsDb, aoaGeometry, LOG);
      aoa.findSoils();

      results().put("aoa_acres", aoa.getArea());
      results().put("mapunits", aoa.toJSON());
    }
  }

  static class AoA extends soils.AoA {

    static final int ALLOWED_SRID = DEFAULT_ASSUMED_SRID;
    static final double DEFAULT_POINT_BUFFER_SIZE = 113.5; // 133.5m buffer = 10 acres.


    /**
     *
     * @param soilsDb
     * @param aoa_geometry
     * @param Log
     * @throws GISObjectException
     * @throws SQLException
     * @throws JSONException
     * @throws IOException
     * @throws ServiceException
     */
    AoA(SOILS_DATA soilsDb, GISObject aoa_geometry, SessionLogger Log) throws Exception {
      super(soilsDb, aoa_geometry, Log);
      if (getSRID() != ALLOWED_SRID)
        throw new ServiceException("Invalid SRID: " + getSRID() + " (only WGS-84 (4326 SRID) is supported.");

      shape.makeValid(GISObject.UsePurpose.all_purposes, GISObject.GISType.all_types);
      if (shape.getGeometry().getGeometryType().equals("Point"))
        shape = shape.buffer(DEFAULT_POINT_BUFFER_SIZE);

      area = shape.areaInAcres();
      if (area > 10000.0)
        throw new ServiceException("Feature area greater than 10,000 acres! This is too large.");
    }


    /**
     *
     * @throws SQLException
     * @throws ServiceException
     * @throws GISObjectException
     */
    public void findSoils() throws Exception {
      if (shape != null)
        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();

      // synonym handling
      ecId = EditQueries.getSynonymFor(editConn.fetchAllSynonyms(), ecId);
      coEcoClass.setEDITPath(EditQueries.getSiteDescrUrl(ecId));

      String edit_es_name = EditQueries.getEcoClassNameFor(editConn.fetchEcoClassList(ecId), ecId);
      if ((edit_es_name != null) && (!edit_es_name.isEmpty())) {
        coEcoClass.ecoclassname(edit_es_name);
      }
    }


    private void getEDITEcoclassNames() throws Exception {
      if (map_units != null) {
        List<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()) {
              if (Utils.isEcoclassId(coEcoClass.ecoclassid()))
                runs.add(() -> queryEdit(coEcoClass, editConn));
            }
          }
        }
        if (!runs.isEmpty())
          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 Exception {
      JSONArray mapunitData = new JSONArray();
      for (MapUnit mapUnit : map_units.values()) {
        if (!mapUnit.isExcluded()) {
          List<GISObject> intersections = mapUnit.getIntersectionPolygons();
          mapUnit.setOutputColumnOrdering(Arrays.asList(
              TableMapUnit.AREASYMBOL_NAME, TableMapUnit.AREA_NAME, TableMapUnit.MUKEY, TableMapUnit.MUNAME,
              TableMapUnit.MUSYM, TableMapUnit.MUACRES, TableMapUnitCalculations.AREA_NAME, TableMapUnitCalculations.AREA_PCT
          ));

          JSONArray mapUnitArray = new JSONArray();
          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()) {
              component.setOutputColumnOrdering(Arrays.asList(
                  TableComponent.COKEY, TableComponent.COMPNAME, TableComponent.COMPPCT_R_NAME,
                  TableComponent.MAJ_COMP_FLAG, TableComponentCalculations.AREA_PCT_NAME, TableComponentCalculations.COMP_AREA_NAME
              ));

              component.setHorizonOutputColumnOrdering(Arrays.asList(TableHorizon.CHKEY_NAME, TableHorizonCalculations.DEPT_R_IN,
                  TableHorizonCalculations.DEPB_R_IN));
              component.setTextureGroupOutputColumnOrdering(Arrays.asList(TableTextureGroup.DESCRIPTION));
              JSONArray componentObject = component.toJSON(false);

              JSONArray ecoClassArray = new JSONArray();
              for (Coecoclass ecoClass : component.ecoClasses().values()) {
                ecoClass.setOutputColumns(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;
    }
  }
}