V1_0.java [src/java/m/example/gis_geotools] Revision: 0eb7802d87405cd03bb930945bc6bcd624982cdb  Date: Mon Jun 06 10:54:08 MDT 2016
/*
 * 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 m.example.gis_geotools;

import com.vividsolutions.jts.geom.Geometry;
import csip.ModelDataService;
import csip.ServiceException;
import java.io.IOException;
import java.util.Map;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import javax.ws.rs.Path;
import oms3.annotations.Description;
import oms3.annotations.Name;
import static org.apache.commons.io.IOUtils.toInputStream;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geojson.geom.GeometryJSON;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;

/**
 *  Basic usage of the GeoTools Geometry and Feature objects.
 *  NOTE:  This example also delves into:
 *         -- SQL statements for MsSQL -vs- PostGIS
 *         -- Demonstration, in the JSON example input files, of the _proper_ usage of "Geometry", "Feature" and "FeatureCollection" types.
 *              Notice in the basic_geometry JSON file there is no keyword "Geometry"...This is correct usage.  "Geometry" is only specified in
 *              "Feature" or GeometryCollection, and has been misused, to date, in our service JSON examples .
 *              The FeatureCollection and Feature types in GeoJSON should be used to their utmost ability, if possible.
 *              Features that have properties should set those properties in their associated Feature properties section
 *              not specify them as separate JSONObjects in the input params.  Using tools like ArcMap or ArcGIS can, subsequently
 *              produce the inputs for our services without outside intervention, if we utilize these GeoJSON features correctly.
 *              Please See:  http://geojson.org/geojson-spec.html for further details...
 *              NOTE:  Thus far, to date, our usage of "Feature" and "FeatureCollection" have been invalid GeoJSON in many cases, and this needs to be repaired,
 *                     for instance, every "Feature" MUST have a "properties" section, but most of ours do not...and most of the time, we really mean 
 *                     to use "Geometry" not "Feature"....
 * 
 * 
 * @author Shaun Case
 */
@Name("GIS_GeoTools")
@Description("An example of how to read in geometries or featurecollections using the geotools libraries.  Also demonstrates proper GeoJSON, error passing with ModelDataServices and SQL differences between PostGIS and MsSQL")
@Path("m/gis_geotools/1.0")
public class V1_0 extends ModelDataService {

    Geometry aoa_geometry;
    JSONObject aoa_input_geometry;
    String exampleQuery1, exampleQuery2, exampleQuery3, exampleQuery4;

    @Override
    //  NOTE:  Changed "Exception" to csip.ServiceException.  (It's considered really bad form to throw a generic exception in Java)
    //         We will check the validity of the param array, if it is present, and return a distinct message identifying what is
    //         missing, if necessary, when we throw a csip.ServiceException. This is much better than just throwing the exception
    //         with no message at all.
    protected void preProcess() throws csip.ServiceException {
        Map<String, JSONObject> inputParams = getParamMap();
        if (null != inputParams) {
            this.aoa_input_geometry = inputParams.get("aoa_geometry");
            if (null == this.aoa_input_geometry) {
                throw new csip.ServiceException("No 'aoa_geometry' item was found in the JSON input.  Please specify an 'aoa_geometry' .");
            }
        } else {
            throw new csip.ServiceException("No JSON input parameters were found.  Please specify some input.");
        }
    }

    @Override   
    protected void doProcess() throws ServiceException {

        GeometryJSON geoJSON = new GeometryJSON();
        try {
            if (this.aoa_input_geometry.has("features")) {
                FeatureJSON featureJSON = new FeatureJSON();

                FeatureCollection inputFeatures = featureJSON.readFeatureCollection(this.aoa_input_geometry.toString());
                FeatureIterator tFeatures = inputFeatures.features();
                int count = 1;
                while (tFeatures.hasNext()) {
                    Feature tFeature = tFeatures.next(); 
                    this.aoa_geometry = (Geometry) ((SimpleFeature) tFeature).getAttribute("geometry");
                    LOG.log(INFO, "Feature geometry " + count + " recieved was:" + this.aoa_geometry.toText());                    
                    count++;
                    //  Do something with this particular feature here...OR just grab the first one if only one was specified.
                    //  This particular code assumes only one was specified, but can easily be altered to send more than one
                    //  and do something with each....
                                
                }
            } else {
                if ( this.aoa_input_geometry.has("type")){  //Try to uncover if this is a "Feature" or a geometry object                    
                    if ( this.aoa_input_geometry.getString("type").equalsIgnoreCase("feature")){
                        FeatureJSON featureJSON = new FeatureJSON();
                        Feature tFeature = featureJSON.readFeature(this.aoa_input_geometry.toString());
                        this.aoa_geometry = (Geometry) ((SimpleFeature) tFeature).getAttribute("geometry");
                        //  Here we could pull out the Feature's properies as well...
                        if ( LOG.isLoggable(INFO) ){
                            LOG.log(INFO, "This Feature''s Id is: " +((SimpleFeature) tFeature).getID());
                            LOG.log(INFO, "This Feature''s name is: " + ((SimpleFeature) tFeature).getAttribute("name"));
                            
                            //NOTE:  For some reason, property values that are arrays (as in the example JSON), don't get read, so watch out for this!
                            LOG.log(INFO, "This Feature''s adjustment factor is:" + ((SimpleFeature) tFeature).getAttribute("AdjustmentFactor"));
                        }
                                                
                    }
                    else{  //Try to read the GeoJSON geometry object
                        this.aoa_geometry = geoJSON.read(toInputStream(this.aoa_input_geometry.toString()));
                    }
                }
                else{
                    
                }
            }

            //  TODO:  Add your code here to do something with the geometry(ies) you read in.
            //         For instance:  See code lines below:  (NOTE:  The .toText() funciton of "Geometry" returns WKT..)
            //  Log the WKT of the input.
            LOG.log(INFO, "Last aoa_geometry value recieved was: " + this.aoa_geometry.toText());
            
            //     PostGIS specific SQL statement below to calculate area in acres.
            exampleQuery1 = "SELECT st_area(st_transform(ST_PolygonFromText('" + this.aoa_geometry.toText() + "', 4326),3541))/43560 as areaInAcres; ";

            //     MsSQL specific SQL statement below to calculate area in acres.  (NOTE:  Assumes point rotation is openGIS compliant, i.e. non-ESRI Shapefile)
            exampleQuery2 = "SELECT  geography::STGeomFromText('" + this.aoa_geometry.toText() + "', 4326).MakeValid().STArea() / 4046.86 as areaInAcres; ";

            //     MsSQL specific SQL statement below to calculate area in acres.  (NOTE:  Assumes point rotation is non-openGIS compliant, i.e. ESRI Shapefile)
            exampleQuery3 = "SELECT  geography::STGeomFromText('" + this.aoa_geometry.toText() + "', 4326).ReorientObject().MakeValid().STArea() / 4046.86 as areaInAcres; ";
            
            //     MsSQL specific SQL statement below to calculate area in acres.  (NOTE:  Example MsSQL Query for unkown rotation types using MsSQL CASE-WHEN Statement in-case rotation type is unknown.)
            exampleQuery4 = "SELECT CASE WHEN geography::STGeomFromText('" + this.aoa_geometry.toText() + "', 4326).MakeValid().EnvelopeAngle() > 90 THEN  geography::STGeomFromText('" + this.aoa_geometry.toText() + "', 4326).MakeValid().ReorientObject().STArea() ELSE geography::STGeomFromText('" + this.aoa_geometry.toText() + "', 4326).MakeValid().STArea() / 4046.86 END as areaInAcres; ";            

        } catch (IOException ex) {
            LOG.log(SEVERE, "Cannot parse the input GeoJSON into valid GIS entities: ", ex);            
            throw new csip.ServiceException("Cannot parse the input GeoJSON into valid GIS entities: ",ex);

        } catch (JSONException ex) {
            LOG.log(SEVERE, "Cannot parse the aoa_geometry geometry type.  Please check your syntax. ", ex);
            throw new csip.ServiceException("Cannot parse the aoa_geometry geometry type.  Please check your syntax. ", ex);

        }
    }
    
    @Override
    protected void postProcess(){
        putResult("example_postgis_sql", exampleQuery1, "Example PostGIS SQL Query using input" );
        putResult("example_mssql_sql_1", exampleQuery2, "Example MsSQL Query for ESRI Shapefile rotation using input" );
        putResult("example_mssql_sql_2", exampleQuery3, "Example MsSQL Query for openGIS compliant rotation using input" );   
        putResult("example_mssql_sql_3", exampleQuery4, "Example MsSQL Query for unkown rotation types using MsSQL CASE-WHEN Statement" ); 
    }
}