GISObjectFactory.java [src/gisobjects] 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-2017, 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 gisobjects;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKTReader;
import static gisobjects.GISObject.DEFAULT_ASSUMED_SRID;
import static gisobjects.GISObject.INPUT_PRECISION;
import gisobjects.db.GISEngine;
import gisobjects.vector.GIS_FeatureCollection;
import gisobjects.vector.GIS_Geometry;
import gisobjects.vector.GIS_GeometryCollection;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.geotools.feature.FeatureCollection;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geojson.geom.GeometryJSON;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;

/**
 *
 * @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
 */
public class GISObjectFactory {

  public static double defaultPrecision = INPUT_PRECISION;

  private static boolean fixBadGeometries = true;
  // private static GeometryFactory geoFactory = new GeometryFactory(new PrecisionModel(Math.pow(10,
  //         ((defaultPrecision != INPUT_PRECISION) ? defaultPrecision : INPUT_PRECISION))), DEFAULT_ASSUMED_SRID);

  private static GISObject createGISObject(byte[] WKB, int SRID) throws GISObjectException {
    GISObject ret_val;

    GeometryFactory geoFactory = new GeometryFactory(new PrecisionModel(Math.pow(10, ((defaultPrecision != INPUT_PRECISION) ? defaultPrecision : INPUT_PRECISION))), SRID);
    WKBReader wkbReader = new WKBReader(geoFactory);
    Geometry geometry;
    try {
      geometry = wkbReader.read(WKB);
    } catch (ParseException ex) {
      throw new GISObjectException("Error parsing WKB into a geometry object: ", ex);
    }

    ret_val = createGISObject(geometry);

    return ret_val;
  }

  private static GISObject createGISObject(String WKT, int SRID) throws GISObjectException {
    GISObject ret_val;

    GeometryFactory geoFactory = new GeometryFactory(new PrecisionModel(Math.pow(10, ((defaultPrecision != INPUT_PRECISION) ? defaultPrecision : INPUT_PRECISION))), SRID);
    WKTReader wktReader = new WKTReader(geoFactory);
    Geometry geometry;
    try {
      geometry = wktReader.read(WKT);
    } catch (ParseException ex) {
      throw new GISObjectException("Error parsing WKT into a geometry object: ", ex);
    }

    ret_val = createGISObject(geometry);

    return ret_val;
  }

  private static GISObject createGISObject(Geometry geometry) throws GISObjectException {
    GISObject ret_val = null;
    if (geometry.isValid()) {
      ret_val = getGISGeometryByType(geometry, null);
    } else {
      if (fixBadGeometries && (geometry.getGeometryType().equals("Polygon") || geometry.getGeometryType().equals("MultiPolygon") || geometry.getGeometryType().equals("GeometryCollection"))) {
        Geometry newGeometry = geometry.buffer(0);
        if (newGeometry.isValid()) {
          ret_val = getGISGeometryByType(newGeometry, null);
          ret_val.hasChanged(true);
        } else {
          throw new GISObjectException("Invalid geometry passed to createGISObject.");
        }
      } else {
        throw new GISObjectException("Invalid geometry passed to createGISObject.");
      }
    }
    return ret_val;
  }

  private static GISObject createGISObject(JSONObject shape) throws JSONException, IOException, GISObjectException {
    return createGISObject(shape, null, DEFAULT_ASSUMED_SRID);
  }

  protected static GISObject getGISGeometryByType(Geometry tGeometry, GISEngine engine) throws GISObjectException {
    GISObject ret_val = null;
    if (null != tGeometry) {
      switch (tGeometry.getGeometryType()) {
        case "GeometryCollection":
          ret_val = new GIS_GeometryCollection(tGeometry, engine);
          break;

        //  Thus far, it appears that we can join all of these into one single type...may need to break them out later.
        case "Point":
        case "MultiPoint":
        case "LineString":
        case "MultiLineString":
        case "Polygon":
        case "MultiPolygon":
          ret_val = new GIS_Geometry(tGeometry, engine);
          break;

        default:
          throw new GISObjectException("Invalid geometry type passed to getGISObjectByType.");
      }
    } else {
      throw new GISObjectException("NULL geometry value passed to getGISObjectByType.  Cannot create a GISObject.");
    }

    return ret_val;
  }

  public static GISObject createGISObjectWKB(String WKB, GISEngine engine) throws GISObjectException {
    return createGISObjectWKB(WKB, engine, DEFAULT_ASSUMED_SRID);
  }

  public static GISObject createGISObjectWKB(String WKB, GISEngine engine, int SRID) throws GISObjectException {
    return createGISObject(WKBReader.hexToBytes(WKB), engine, SRID);
  }

  public static GISObject createGISObject(byte[] WKB, GISEngine engine) throws GISObjectException {
    return createGISObject(WKB, engine, DEFAULT_ASSUMED_SRID);
  }

  public static GISObject createGISObject(byte[] WKB, GISEngine engine, int SRID) throws GISObjectException {
    GISObject ret_val = createGISObject(WKB, SRID);
    if (null != ret_val) {
      ret_val.setGISEngine(engine);
    }
    return ret_val;
  }

  public static GISObject createGISObject(String WKT, GISEngine engine) throws GISObjectException {
    return createGISObject(WKT, engine, DEFAULT_ASSUMED_SRID);
  }

  public static GISObject createGISObject(String WKT, GISEngine engine, int SRID) throws GISObjectException {
    GISObject ret_val = createGISObject(WKT, SRID);
    if (null != ret_val) {
      ret_val.setGISEngine(engine);
    }
    return ret_val;
  }

  public static GISObject createGISObject(Geometry geometry, GISEngine engine) throws GISObjectException {
    GISObject ret_val;
    ret_val = createGISObject(geometry);
    ret_val.setGISEngine(engine);
    return ret_val;
  }

  public static GISObject createGISObject(JSONObject shape, GISEngine tEngine) throws JSONException, IOException, GISObjectException {
    GISObject ret_val = createGISObject(shape, tEngine, DEFAULT_ASSUMED_SRID);
    return ret_val;
  }

  public static GISObject createGISObject(JSONObject shape, GISEngine tEngine, int defaultSRID) throws JSONException, IOException, GISObjectException {
    GISObject ret_val = null;  //Default value if parsing falls-through below.

    if ((null != shape) && (shape.has("type"))) {
      String geoJSONType = shape.optString("type");
      if (geoJSONType.length() > 0) {
        switch (geoJSONType) {
          case "FeatureCollection": {
            FeatureJSON featureJSON = new FeatureJSON();
            FeatureCollection inputFeatures = featureJSON.readFeatureCollection(shape.toString());
            ret_val = new GIS_FeatureCollection(inputFeatures, tEngine, fixBadGeometries, defaultSRID);
          }

          break;
          case "Feature": {
            // Create a Feature GISObject here...(same as basic Geometry type, but with "properties")
            FeatureJSON featureJSON = new FeatureJSON();
            Feature tFeature = featureJSON.readFeature(shape.toString());
            //GeometryFactory geoFactory = new GeometryFactory(new PrecisionModel(Math.pow(10, INPUT_PRECISION)), defaultSRID);
            GeometryFactory geoFactory = new GeometryFactory(new PrecisionModel(Math.pow(10,
                ((defaultPrecision != INPUT_PRECISION) ? defaultPrecision : INPUT_PRECISION))), defaultSRID);
            Geometry tGeometry = geoFactory.createGeometry((Geometry) ((SimpleFeature) tFeature).getAttribute("geometry"));
            if (tGeometry.isValid()) {
              ret_val = getGISGeometryByType(tGeometry, tEngine);
            } else if (fixBadGeometries) {
              ret_val = createGISObject(tEngine.makeValid(tGeometry.toString()), tEngine);
              if ((null == ret_val) || !ret_val.isValid()) {
                throw new GISObjectException("Invalid geometry specified for this feature.");
              } else {
                ret_val.hasChanged(true);
              }
            } else {
              throw new GISObjectException("Invalid geometry specified for this feature.");
            }

            //TODO:  Iterate over the properties here and place into the GISObject returned; (Maybe...)  Will need a GIS_Feature class at that time.
            //       For now, we do not make use of the "Properties" to pass information in our JSON input packages for csip,
            //       however, in the future, this may become necessary.  Currently, each property that we wish to know for each "Feature"
            //       is specified separately within the JSON input array at the same level as each "Feature".                        
          }
          break;

          case "GeometryCollection":
          case "Polygon":
          case "MultiPolygon":
          case "Point":
          case "MultiPoint":
          case "LineString":
          case "MultiLineString": {
            //GeometryFactory geoFactory = new GeometryFactory(new PrecisionModel(Math.pow(10, INPUT_PRECISION)), defaultSRID);
            GeometryJSON geoJSON = new GeometryJSON();
            Geometry newGeometry = geoJSON.read(new ByteArrayInputStream(shape.toString().getBytes()));
            Geometry tGeometry;
            GeometryFactory geoFactory = new GeometryFactory(new PrecisionModel(Math.pow(10,
                ((defaultPrecision != INPUT_PRECISION) ? defaultPrecision : INPUT_PRECISION))), defaultSRID);
            tGeometry = geoFactory.createGeometry(newGeometry);
            if (tGeometry.isValid()) {
              ret_val = getGISGeometryByType(tGeometry, tEngine);
            } else if (fixBadGeometries) {
              ret_val = createGISObject(tEngine.makeValid(tGeometry.toString()), tEngine);
              if ((null == ret_val) || !ret_val.isValid()) {
                ret_val = createGISObject(ret_val.getGeometry().convexHull());
                if (!ret_val.isValid()) {
                  throw new GISObjectException("Invalid geometry specified for this feature.");
                } else {
                  ret_val.hasChanged(true);
                }
              }
            } else {

              throw new GISObjectException("Invalid geometry specified for this feature.");
            }
          }
          break;

          default:
            throw new GISObjectException("Invalid GeoJSON type value specified.  Cannot parse into a GISObject.");

        }

      }
    }

    if (null == ret_val) {
      throw new GISObjectException("Invalid JSONObject passed to createGISObject.  Cannot create a GISObject.");
    }

    ret_val.setGISEngine(tEngine);
    return ret_val;
  }

  //public void setGISObjectEngine(GISEngine engine) {
  //    engine = engine;
  //}
  public static void setFixBadGeometries(boolean fixBadGeometries) {
    GISObjectFactory.fixBadGeometries = fixBadGeometries;
  }
}