PostgresqlGISDatabase.java [src/m/utils] Revision: default 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 m.utils;
import com.vividsolutions.jts.algorithm.MinimumDiameter;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.io.WKTWriter;
import csip.api.server.ServiceException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.postgresql.util.PSQLException;
/**
*
* @author ktraff
*/
public class PostgresqlGISDatabase extends PostgresqlDatabase implements IGISDatabase {
public static final String DEFAULT_SCHEMA = "csip_gis";
public PostgresqlGISDatabase(String hostname, String port, String dbName, String username, String password) throws ServiceException {
super(hostname, port, dbName, username, password);
}
public PostgresqlGISDatabase(Connection conn) throws Exception {
super(conn);
}
public String getSessionID() {
return SESSION_ID;
}
public String getDefaultCRS() {
return Database.DEFAULT_CRS;
}
public String getDefaultSchema() {
return DEFAULT_SCHEMA;
}
@Override
public int deleteLayer(Layer lyr) throws ServiceException {
return deleteTable(lyr.getSchemaTable());
}
@Override
public Layer createLayer(String tableName, JSONObject layerJSON) throws ServiceException {
Layer lyr = null;
try {
GeoJSONParser.ShapeType shapeType = GeoJSONParser.getShape(layerJSON);
String schemaTable = getDefaultSchema() + ".\"" + tableName + "\"";
Statement stmt = conn.createStatement();
if (!exists(getDefaultSchema(), tableName)) {
String sql = "CREATE TABLE " + schemaTable + " (id serial)";
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
sql = "SELECT AddGeometryColumn ('" +
getDefaultSchema() + "','" + tableName + "', 'the_geom', " +
"4326, '" + shapeType + "', 2);";
LOG.log(Level.INFO, sql);
stmt.executeQuery(sql);
}
lyr = new Layer(getDefaultSchema(), tableName, this);
stmt.close();
} catch (JSONException|SQLException ex) {
throw new csip.api.server.ServiceException(ex);
}
return lyr;
}
@Override
public Layer copyLayer(Layer lyr, String schemaName, String tableName) throws Exception {
Table tbl = super.copyTable((Table)lyr, schemaName, tableName);
return new Layer(tbl.schema, tbl.table, this);
}
@Override
public Layer addGeometryColumn(Table tbl, String shapeType) throws Exception {
Statement stmt = conn.createStatement();
String sql = "SELECT AddGeometryColumn ('" +
tbl.schema + "','" + tbl.table + "', 'the_geom', " +
"4326, '" + shapeType + "', 2);";
LOG.log(Level.INFO, sql);
stmt.executeQuery(sql);
stmt.close();
return new Layer(tbl.schema, tbl.table, this);
}
@Override
public Feature uploadFeature(JSONObject featureJSON, Layer layer) throws csip.api.server.ServiceException {
try {
Statement stmt = conn.createStatement();
JSONObject geomJSON = featureJSON.getJSONObject("geometry");
if (!GeoJSONParser.hasCRS(geomJSON)) {
geomJSON.put("crs", featureJSON.get("crs"));
}
String sql = "SELECT ST_Transform(ST_GeomFromGeoJSON('" +
geomJSON.toString() + "'), 4326) AS geojson";
LOG.log(Level.INFO, sql);
ResultSet rs = stmt.executeQuery(sql);
rs.next();
String geom = rs.getString("geojson");
// Create a mapping of ordered column values for insertion.
ArrayList<String> cols = new ArrayList<>();
ArrayList<Object> vals = new ArrayList();
cols.add("the_geom");
vals.add(geom);
// Create column fields for all properties.
if (!featureJSON.isNull("properties")) {
ArrayList<String> columnNames = getColumns(getDefaultSchema(), layer.table);
JSONObject props = featureJSON.getJSONObject("properties");
Iterator<?> keys = props.keys();
while (keys.hasNext()) {
String key = (String)keys.next();
LOG.log(Level.INFO, "Found property " + key + ": " + props.get(key));
cols.add(key);
vals.add(props.get(key));
if (!columnNames.contains(key)) {
if (props.get(key) instanceof String) {
addColumn(getDefaultSchema(), layer.table, key, DataType.STRING);
}
else if (props.get(key) instanceof Double ||
props.get(key) instanceof Integer ||
props.get(key) instanceof Number) {
addColumn(getDefaultSchema(), layer.table, key, DataType.NUMERIC);
}
}
}
}
else {
LOG.log(Level.INFO, "Found no properties");
}
sql = "INSERT INTO " + layer.getSchemaTable() + " (" + LocalStringUtils.join(cols, ",") + ") " +
"VALUES ('" + LocalStringUtils.join(vals, "', '") + "') RETURNING id";
LOG.log(Level.INFO, sql);
stmt.execute(sql);
ResultSet lastUpdated = stmt.getResultSet();
int id = -1;
if (lastUpdated.next()) {
id = lastUpdated.getInt(1);
}
LOG.log(Level.INFO, "Uploaded feature with id: " + id);
stmt.close();
return new Feature(layer, featureJSON, id);
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public int findSRID(Layer lyr) throws ServiceException {
try {
int srid = -1;
Statement stmt = conn.createStatement();
String sql = "SELECT Find_SRID('" +
lyr.schema + "', '" +
lyr.table + "', 'the_geom') as srid";
LOG.log(Level.INFO, sql);
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
srid = rs.getInt("srid");
}
stmt.close();
return srid;
} catch (SQLException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public Layer intersect(Layer one, Layer two) throws Exception {
String table = "intsn_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(getDefaultSchema());
deleteTable(schemaTable);
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_Intersection(" + one.getSchemaTable() + ".the_geom, " + two.getSchemaTable() + ".the_geom) AS the_geom ",
getProperties(one, true),
getProperties(two, true),
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + one.getSchemaTable() + ", " + two.getSchemaTable() + " " +
"WHERE " + one.getSchemaTable() + ".the_geom && " + two.getSchemaTable() + ".the_geom";
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
}
@Override
public boolean coveredBy(Layer one, Layer two) throws Exception {
boolean coveredBy = false;
Statement stmt = conn.createStatement();
String sql = "SELECT ST_CoveredBy(" + one.getSchemaTable() + ".the_geom, " + two.getSchemaTable() + ".the_geom) AS result " +
"FROM " + one.getSchemaTable() + ", " + two.getSchemaTable() + " ";
LOG.log(Level.INFO, sql);
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
coveredBy = rs.getBoolean("result");
}
stmt.close();
return coveredBy;
}
@Override
public boolean intersects(Layer one, Layer two) throws Exception {
boolean intersects = false;
Statement stmt = conn.createStatement();
String sql = "SELECT ST_Intersects(" + one.getSchemaTable() + ".the_geom, " + two.getSchemaTable() + ".the_geom) " +
"FROM " + one.getSchemaTable() + ", " + two.getSchemaTable() + " ";
LOG.log(Level.INFO, sql);
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
intersects = rs.getBoolean("st_intersects");
}
stmt.close();
return intersects;
}
@Override
public boolean sameGeometry(Layer one, Layer two) throws Exception {
boolean sameGeometry = false;
Statement stmt = conn.createStatement();
String sql = "SELECT ST_Equals(" + one.getSchemaTable() + ".the_geom, " + two.getSchemaTable() + ".the_geom) AS result " +
"FROM " + one.getSchemaTable() + ", " + two.getSchemaTable() + " ";
LOG.log(Level.INFO, sql);
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
sameGeometry = rs.getBoolean("result");
}
stmt.close();
return sameGeometry;
}
@Override
public Layer difference(Layer one, Layer two) throws Exception {
return difference(one, two, new ArrayList<String>(Arrays.asList(new String[]{"id", "the_geom"})));
}
@Override
public Layer difference(Layer one, Layer two, ArrayList<String> colsExclude) throws Exception {
String table = "diff_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(getDefaultSchema());
deleteTable(schemaTable);
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_Difference(" + one.getSchemaTable() + ".the_geom, " + two.getSchemaTable() + ".the_geom) AS the_geom ",
getProperties(one, true, colsExclude),
getProperties(two, true, colsExclude),
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + one.getSchemaTable() + ", " + two.getSchemaTable();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
}
@Override
public Layer union(Layer one, Layer two) throws ServiceException {
try {
String table = "union_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(getDefaultSchema());
deleteTable(schemaTable);
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_Union(" + one.getSchemaTable() + ".the_geom, " + two.getSchemaTable() + ".the_geom) AS the_geom ",
getProperties(one, true),
getProperties(two, true),
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + one.getSchemaTable() + ", " + two.getSchemaTable();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public Layer buffer(Layer lyr, int distance) throws ServiceException {
try {
String table = "buf_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(getDefaultSchema());
deleteTable(schemaTable);
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_Buffer(" + lyr.getSchemaTable() + ".the_geom::geography, " + distance + ")::geometry AS the_geom ",
getProperties(lyr, true)
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + lyr.getSchemaTable();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public Layer minimumBoundingCircle(Layer lyr, int numSegsPerQtCirc) throws ServiceException {
try {
String table = "mbc_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(getDefaultSchema());
deleteTable(schemaTable);
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_MinimumBoundingCircle(ST_Collect(" + lyr.getSchemaTable() + ".the_geom), " +
numSegsPerQtCirc + ") AS the_geom ",
getProperties(lyr, true)
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + lyr.getSchemaTable();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public Layer minimumBoundingRectangle(Layer lyr) throws ServiceException {
try {
String table = "mbr_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
JTSAdapter adapter = new JTSAdapter(this);
String wkt = adapter.minimumBoundingRectangle(lyr);
LOG.log(Level.INFO, "Minimum Bounding Rectangle: " + wkt);
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_GeomFromText('" + wkt + "') as the_geom ",
getProperties(lyr, true)
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + lyr.getSchemaTable();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public Layer transform(Layer lyr, int SRID) throws ServiceException {
try {
String table = "tfm_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_Transform(" + lyr.getSchemaTable() + ".the_geom, " + SRID + ") as the_geom ",
getProperties(lyr, true)
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + lyr.getSchemaTable();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public Layer extent(Layer lyr) throws ServiceException {
try {
String table = "ext_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(getDefaultSchema());
deleteTable(schemaTable);
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_Extent(" + lyr.getSchemaTable() + ".the_geom)::geometry AS the_geom ",
getProperties(lyr, true)
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + lyr.getSchemaTable();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public Layer envelope(Layer lyr) throws ServiceException {
try {
String table = "env_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(getDefaultSchema());
deleteTable(schemaTable);
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_Envelope(ST_Collect(" + lyr.getSchemaTable() + ".the_geom)) AS the_geom ",
getProperties(lyr, true)
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + lyr.getSchemaTable();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public Layer area(Layer lyr) throws ServiceException {
try {
String table = "area_" + getSessionID() + "_" + new RandomString(4).nextString();
String schemaTable = getDefaultSchema() + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(getDefaultSchema());
deleteTable(schemaTable);
ArrayList<String> colsExclude = new ArrayList<>(Arrays.asList(new String[]{"the_geom","id","calc_area"}));
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
lyr.getSchemaTable() + ".the_geom ",
"ST_Area(" + lyr.getSchemaTable() + ".the_geom::geography) AS calc_area ",
getProperties(lyr, true, colsExclude)
}));
String sql = "CREATE TABLE " + schemaTable + " AS " +
"SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + lyr.getSchemaTable();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
Layer res = new Layer(getDefaultSchema(), table, this);
updateGeometrySRID(res, GeoJSONParser.getSRIDInt(getDefaultCRS()));
stmt.close();
return res;
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
public void updateGeometrySRID(Layer lyr, int SRID) throws ServiceException {
try {
Statement stmt = conn.createStatement();
String sql = "SELECT UpdateGeometrySRID('" +
lyr.schema + "', '" + lyr.table + "', 'the_geom', " +
SRID + ")";
LOG.log(Level.INFO, sql);
stmt.execute(sql);
} catch (SQLException ex) {
throw new ServiceException(ex);
}
}
public double[] parseBBox(String bbox) {
double [] res = new double[4];
Pattern pattern = Pattern.compile("BOX\\(([0-9\\.-]+)\\s([0-9\\.-]+),([0-9\\.-]+) ([0-9\\.-]+)\\)");
Matcher matcher = pattern.matcher(bbox);
matcher.matches();
for(int i=1; i<=matcher.groupCount(); i++){
res[i-1] = Double.parseDouble(matcher.group(i));
}
return res;
}
public JSONObject toJSON(Layer lyr) throws ServiceException {
try {
Statement stmt = conn.createStatement();
ArrayList<String> cols = getColumns(lyr.schema, lyr.table);
JSONObject featureCollection = new JSONObject("{" +
"\"type\": \"FeatureCollection\"," +
"}");
JSONArray features = new JSONArray();
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_AsGeoJSON(" + lyr.getSchemaTable() + ".the_geom) As geometry ",
getProperties(lyr, false),
}));
String sql = "SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + lyr.getSchemaTable() + " ";
LOG.log(Level.INFO, sql);
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
JSONObject feat = new JSONObject();
feat.put("type", "Feature");
feat.put("geometry", new JSONObject(rs.getString("geometry")));
JSONObject jsonProps = new JSONObject();
for (String col : cols) {
if (!col.equals("the_geom")) {
try {
jsonProps.put(col, rs.getObject(col));
} catch (PSQLException ex) {
LOG.log(Level.WARNING, "Could not find column: " + col + " in result set (skipping).");
}
}
}
feat.put("properties", jsonProps);
features.put(feat);
}
stmt.close();
featureCollection.put("features", features);
return featureCollection;
} catch (SQLException|JSONException ex) {
throw new csip.api.server.ServiceException(ex);
}
}
@Override
public String toWKT(Layer lyr) throws ServiceException {
String wkt = "";
try {
Statement stmt = conn.createStatement();
ArrayList<String> props = new ArrayList<>(Arrays.asList(new String[]{
"ST_AsText(" + lyr.getSchemaTable() + ".the_geom) As wkt "
}));
String sql = "SELECT " + LocalStringUtils.join(props, ", ") + " " +
"FROM " + lyr.getSchemaTable() + " ";
LOG.log(Level.INFO, sql);
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
wkt = rs.getString("wkt");
}
} catch (SQLException ex) {
throw new csip.api.server.ServiceException(ex);
}
return wkt;
}
@Override
public JSONObject toJSON(Layer lyr, int outputSRID) throws ServiceException {
try {
int SRID = findSRID(lyr);
if (SRID != outputSRID) {
LOG.log(Level.INFO, "Translating " + lyr.toString() + " from " +
SRID + " to " + outputSRID);
lyr = transform(lyr, outputSRID);
}
return toJSON(lyr);
} catch (ServiceException ex) {
throw ex;
}
}
}
/**
* Interfaces with the Java Topology Library
* @author ktraff
*/
class JTSAdapter {
protected final Logger LOG = Logger.getLogger(getClass().getSimpleName());
IGISDatabase db;
public JTSAdapter(IGISDatabase db) {
this.db = db;
}
public String minimumBoundingRectangle(Layer lyr) throws ServiceException {
try {
String wkt = db.toWKT(lyr);
LOG.log(Level.INFO, "WKT: " + wkt);
Geometry geom = new WKTReader().read(wkt);
MinimumDiameter helper = new MinimumDiameter(geom);
return new WKTWriter().write(helper.getMinimumRectangle());
} catch (ParseException ex) {
throw new ServiceException(ex);
}
}
}