V1_0.java [src/java/m/rse/wepot] Revision:   Date:
package m.rse.wepot;

import csip.ModelDataService;
import csip.ServiceException;
import csip.annotations.Polling;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collection;
import javax.ws.rs.Path;
import m.rse.cfactor.utils.Const;
import m.rse.cfactor.utils.PGTools;
import oms3.annotations.Description;
import oms3.annotations.Name;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

/**
 *
 * @author Shaun Case
 * @author wl
 */
//@Name("wepot")
//@Description("This service computes water and wind erodibility potentials for an area of analysis (AoA).  The service clips SSURGO soil mapunits with AoA geometry, determines the dominant soil component in the AoA, gets parameters from the SSURGO component table, including a climate factor from the C Factor layer, and computes the following equations:  Wind Erosion Potential = C*I/T ;  Water Erosion Potential = K*(LS)/T")
//@Path("m/wepot/1.0")
//@Polling(first = 5000, next = 2000)
@Deprecated

public class V1_0 extends ModelDataService {
    
    //Do we need these for RSE-02?  (taken from RSE-01)
    //static final String ALASKA_REGION_WARNING_MSG = "Coordinate is in Alaska region.  C-Factor value determination is pending further design specification from USDA-NRCS";
    //static final String SERVICE_DESC = "Climatic erosivity reflecting wind speed and surface soil moisture; value 100 at Garden City, Kansas";   
    //static final double ALASKA_MIN_LATITUDE = 50.00;
    //static final double UNKNOWN_CFACTOR = -9999.99;
    
    static final int JSON_LATITUDE = 1;
    static final int JSON_LONGITUDE = 0;
    static final String RSE_AOAID = "AoAId";
    static final String AOA_GEOMETRY = "aoa_geometry";	    

  
    private String AoAId;
    private JSONObject aoaGeometry;
    private PGTools.PolygonLatLon polygonData;
    private String error_msg;
    
    //Result variables
    private double aoa_cfactor; 
    private String aoa_dom_comp;
    private String aoa_dom_compname;
    private double aoa_ifactor;
    private double aoa_tfactor;
    private double aoa_lsfactor;
    private double aoa_kfactor;
    private double aoa_wind_ep;
    private double aoa_water_ep;
    
    double cfactorraster = -1;

    
@Override
    protected void preProcess(){
	this.error_msg = "";
	this.AoAId = "Error";
	this.polygonData = null;	
	this.aoaGeometry = null;
	
	this.aoa_cfactor = Const.UNKNOWN_CFACTOR; 
	this.aoa_dom_comp = "";
	this.aoa_dom_compname = "";
	this.aoa_ifactor = Const.UNKNOWN_CFACTOR;
	this.aoa_tfactor = Const.UNKNOWN_CFACTOR;
	this.aoa_lsfactor = Const.UNKNOWN_CFACTOR;
	this.aoa_kfactor = Const.UNKNOWN_CFACTOR;
	this.aoa_wind_ep = Const.UNKNOWN_CFACTOR;
	this.aoa_water_ep = Const.UNKNOWN_CFACTOR;
	
	
	
	try{
	    //These functions will throw a usuable user message if they cannot find these values.
	    //  Just catch it and return it to the user.
	    this.AoAId = getStringParam( V1_0.RSE_AOAID );
	    this.aoaGeometry = getJSONParam( V1_0.AOA_GEOMETRY );  
	}
	catch( ServiceException ex ){
	    this.error_msg = "Cannot process request JSON:  " + ex.getMessage();
	}
    }

    @Override
    protected String process(){
	try{
            

	    if ( this.error_msg.isEmpty() ){
		JSONArray features = this.aoaGeometry.optJSONArray("features");
		if ( this.buildPolygon( features ) ){		  					   
		    //Got a valid Polygon now.  
		    //HINT:  You can get the WKT from the PolygonLatLon object to build an SQL query via the "toWKT" funciton, 
		    //       and then encasing that WKT in the typical "geometry::STPolyFromText(<KWT goes here>,4326)", etc.  

//TODO:  Call raster layer intersect code here ( to be determined by Wes )
                    LOG.info("THE POLYGON AS WE KNOW IT IS=" + this.polygonData.toWKT());
                    String cfactor_polygon  = "geometry::STPolyFromText(" + this.polygonData.toWKT() + ",4326)";
                    LOG.info("getting centroid for polygon now");
                    PGTools.Centroid centroid = PGTools.getCentroid(cfactor_polygon, LOG);
                    LOG.info("About to get C Factor Raster value for lat=" + centroid.lat + " long=" + centroid.lon + " ");
                           
                    this.aoa_cfactor = PGTools.getCFactorRaster(centroid.lat, centroid.lon, getWorkspaceDir(), LOG);
		    //this.aoa_cfactor = Const.UNKNOWN_CFACTOR;  //  Change to the result of the function call to be written
			    
		    //Call SSURGO gemoetry intersect code here ( ssurgo.soilmu_a table )
		    //Call SSURGO component lookups here ( ssurgo.component table )		    
		    if ( this.componentLookup( this.ssurgoIntersect( this.polygonData.toWKT() ) )){
			//Calc wind and water ep here...
			this.aoa_wind_ep = this.aoa_cfactor * this.aoa_ifactor / this.aoa_tfactor;
			this.aoa_water_ep = this.aoa_kfactor * this.aoa_lsfactor / this.aoa_tfactor;
		    }
		}
	    }
	}
	catch( JSONException ex ){
	    this.error_msg += "Cannot proceed with processing the request:  " + ex.getMessage();
	}
        catch (SQLException se) {
            this.error_msg += "SQL exception getting cfactor:  " + se.getMessage();
        }
        catch (ServiceException sx) {
            this.error_msg += "Service exception getting cfactor:  " + sx.getMessage();
        }
        catch (IOException ioe) {
            this.error_msg += "IO Exception getting cFactor value from raster geotiff layer:  " + ioe.getMessage();
        }
            
	
	return ( this.error_msg.isEmpty()? EXEC_OK : this.error_msg );
    }   
    
    @Override
    protected void postProcess() throws Exception {
	if ( this.error_msg.isEmpty() ){	    
	    putResult( "aoa_id", this.AoAId );
	    putResult( "aoa_dom_comp", this.aoa_dom_comp );
	    putResult( "aoa_dom_compname", this.aoa_dom_compname );
	    putResult( "aoa_cfactor", this.aoa_cfactor );
	    putResult( "aoa_ifactor", this.aoa_ifactor );
	    putResult( "aoa_tfactor", this.aoa_tfactor );
	    putResult( "aoa_lsfactor", this.aoa_lsfactor );
	    putResult( "aoa_kfactor", this.aoa_kfactor );
	    putResult( "aoa_wind_ep", this.aoa_wind_ep );
	    putResult( "aoa_water_ep", this.aoa_water_ep ); 
	}
    }   
   
    
    //Private functions
    private Boolean buildPolygon( JSONArray features ) throws JSONException{
	Boolean ret_val = false;
	
	if ( null != features ){
	    if ( features.length() > 0 ){
		JSONObject feature = features.getJSONObject( 0 ).optJSONObject( "geometry" ) ;
		if ( this.isGeometryPolygonType( feature ) ){
		    JSONArray polygon = feature.optJSONArray( "coordinates" );
		    if ( null != polygon ){				   
			if ( polygon.length() > 0 ){
			    this.polygonData = readPolygonCoordinates( polygon.getJSONArray( 0 ) );
			    if ( null != this.polygonData ){
				if ( !this.polygonData.isValid() ){
				    this.error_msg += "Invalid latitude and/or longitude data contained in this polygon.  Cannot proceed with processing of this request. ";		    
				}
				else{
				    ret_val = true;
				}
			    }//No else needed here, the error message will be built in readPolygonCoordinates()
			}
			else{
			    this.error_msg = "No coordinates found associated with the polygon specified in feature collection number:  1 . ";			   
		       }		       
		    }
		    else{
			this.error_msg = "No coordinates found associated with the polygon specified in feature collection number:  1 . ";
		    }		    
		}//No else needed here, the error message will be built in isGeometryPolygonType()
	    }
	    else{
		this.error_msg = "No geometry found associated with this feature. ";		
	    }
	}
	else{
	    this.error_msg = "Cannot process request JSON, missing features. ";
	}	

	return ret_val;
    }
    
    private Boolean isGeometryPolygonType( JSONObject geometry ){
	Boolean ret_val = false;
	
	if ( null != geometry ){
	    String geometryType;

	    geometryType = geometry.optString( "type" );
	    if ( !geometryType.isEmpty() ){
		if ( geometryType.toLowerCase().equals( "polygon" ) ){
		    ret_val = true;
		}
		else{
		    this.error_msg = "No valid geometry type found in the feature collection number:  1 .  Looking for 'Polygon'. ";			
		}
	    }
	    else{
		this.error_msg = "No geometry type specified in the feature collection number:  1 . ";				    
	    }			    
	}
	else{
	    this.error_msg = "No geometry found in the feature collection number:  1 . ";
	}	
	
	return ret_val;
    }
    
    private PGTools.PolygonLatLon readPolygonCoordinates( JSONArray shape ) throws JSONException{
	PGTools.PolygonLatLon tPolygon = new PGTools.PolygonLatLon();
	
	if ( null != shape ){
	    for ( int k = 0; k < shape.length(); k++ ){
		JSONArray jPoint = shape.getJSONArray( k );
		tPolygon.add(  jPoint.getDouble( V1_0.JSON_LATITUDE ), jPoint.getDouble( V1_0.JSON_LONGITUDE ) );
	    }	     		   
	}
	else{
	    this.error_msg = "Cannot find the latitude and longitude values for this polygon. ";
	    tPolygon = null;
	}	
		
	return tPolygon;
    }
    
    // Returns the intersected mukey with the largest area.
    private String ssurgoIntersect( String WKTPolygon ){
	HashMap<String, V1_0.ssurgoAttributes> mukeyAttributes;
	ArrayList<V1_0.ssurgoAttributes> attributeList;	
	String polygonText = " ST_PolygonFromText(" + WKTPolygon + ") ";
	String query;
	Connection conn;
	Statement statement;
	String ret_val = null;
	
	try{
	    if ( null != WKTPolygon ){
		mukeyAttributes = new HashMap<>();
		attributeList = new ArrayList<>();

		query = "SELECT m.areasymbol, m.musym, m.mukey,"
			+ "st_area(st_transform(st_intersection(ST_PolygonFromTexT(" + WKTPolygon + ", 4326), m.the_geom::geography::geometry ),3541))/43560 as sizeIntersectionAcres "
			+ " FROM ssurgo.soilmu_a as m "
			+ " WHERE ST_Intersects(" + polygonText + ", m.the_geom ) "
			+ " AND st_isvalid( m.the_geom) AND st_isvalid(" + polygonText + ");";

                LOG.info("ssurgo query=" + query);
		conn = PGTools.getConnection( "ssurgo", LOG );
		statement = conn.createStatement();
		ResultSet results = statement.executeQuery( query );

		if ( null != results ){
		    while ( results.next() ){
			V1_0.ssurgoAttributes tAttributes;
			String tKey = results.getString( "areasymbol" ) + ":" + results.getString( "mukey" );

			tAttributes = mukeyAttributes.get( tKey );
			
			//Merge, i.e. sum, areas of duplicate AoA:mukey combinations
			if ( null != tAttributes ){
			    tAttributes.gid_area += results.getDouble( "sizeIntersectionAcres" );
			}
			else{
			    tAttributes = new V1_0.ssurgoAttributes();
			    tAttributes.gid = results.getString( "musym" );
			    tAttributes.AoA_Id = results.getString( "areasymbol" );
			    tAttributes.mukey = results.getString( "mukey" );
			    tAttributes.gid_area = results.getDouble( "sizeIntersectionAcres" );			    
			    
			    mukeyAttributes.put( tKey, tAttributes );
			    attributeList.add( tAttributes );
			}
		    }
		    //  Have a unique set of mukeys now, let's find the largest
		    double largestArea = 0.0;
		    
		    for( V1_0.ssurgoAttributes tAttribute : attributeList ){
			if ( largestArea < tAttribute.gid_area ){
			    largestArea = tAttribute.gid_area;
			    ret_val = tAttribute.mukey;
			}
		    }
		}
		else{
		    this.error_msg += "No results from the intersect query for this geometry. ";
		    ret_val = null;
		}
	    }
	}
	catch( SQLException | csip.ServiceException ex ){
	    this.error_msg += "Cannot continue processing this request in the intersect procedure:  " + ex.getMessage();	
	    ret_val = null;	    
	}
	
	
	return ret_val;
    }
    
    private Boolean componentLookup( String mukey ){
	Connection conn;
	Statement statement;
	String query;
	ResultSet results;
	Boolean ret_val = false;
	
	try{
	    if ( ( null != mukey ) && !mukey.isEmpty() ){
		conn = PGTools.getConnection( "ssurgo", LOG );
		statement = conn.createStatement();
		
		query = "SELECT ssurgo.component.cokey, ssurgo.component.compname, ssurgo.component.wei as ifact, ssurgo.component.slopelenusle_r as lsfact, ssurgo.component.tfact, ssurgo.component.comppct_r "
			+ " FROM ssurgo.component WHERE ssurgo.component.mukey='" + mukey + "' "
			+ " ORDER BY ssurgo.component.comppct_r DESC;";
                LOG.info (" ssurgo query=" + query);
		
		results = statement.executeQuery( query );
		
		if ( null != results ){
		    results.next(); //Only need the first result row, since it will contain the larges comppct_r value, due to the order by clause.
		    
		    this.aoa_dom_comp = results.getString( "cokey" );
		    this.aoa_dom_compname = results.getString( "compname" );
		    this.aoa_ifactor = results.getDouble( "ifact" );
		    this.aoa_lsfactor = results.getDouble( "lsfact" );
		    this.aoa_tfactor = results.getDouble( "tfact" );
		    
		    results.close();
		    		    
		    query = "SELECT ssurgo.chorizon.cokey, ssurgo.chorizon.kffact, ssurgo.chorizon.kwfact FROM ssurgo.chorizon WHERE ssurgo.chorizon.cokey='" + this.aoa_dom_comp + "'";
                    LOG.info( " ssurgo query=" + query);

                    results = statement.executeQuery( query );
		    
		    if ( null != results ){				
			results.next();
			
			this.aoa_kfactor = results.getDouble( "kffact" );
			if ( results.wasNull() ){
			    this.aoa_kfactor = results.getDouble( "kwfact" );
			    if ( results.wasNull() ){
				this.aoa_kfactor = 0.02;
			    }			   		    
			}
			
			ret_val = true;						
		    }
		    else{
			this.error_msg += "Could not find a matching cokey for this intersected mapunit in the cHorizon table.";			
		    }
		    
		}
		else{
		    this.error_msg += "Could not find a matching cokey for this intersected mapunit.";
		}
		
	    }
	}
	catch( SQLException | csip.ServiceException ex ){
	    this.error_msg += "Could not continue processing the component lookups for this mapunit intersect:  " + ex.getMessage();
	}
	
	return ret_val;
    }
    
    
    //Inner Classes/Structures go here...
    
    /**
     *
     */
        
    public static class ssurgoAttributes{
	public String gid;
	public String AoA_Id;
	public String mukey;
	public double gid_area;
    }    
    
}