PostgresqlDatabase.java [src/java/m/gis] Revision: 5b3396ac1351925498bb50aecb78a32c598fd032 Date: Mon Jun 22 14:58:18 MDT 2015
/*
* 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.gis;
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.ServiceException;
import java.awt.font.NumericShaper;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
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;
/**
*
* @author ktraff
*/
public class PostgresqlDatabase extends Database {
public PostgresqlDatabase(String hostname, String port, String dbName, String username, String password) throws ServiceException {
super(hostname, port, dbName, username, password);
createSchema(SCHEMA);
}
@Override
public void createSchema(String schemaName) throws ServiceException {
try {
Statement stmt = conn.createStatement();
String sql = "CREATE SCHEMA IF NOT EXISTS " + schemaName;
stmt.executeUpdate(sql);
stmt.close();
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public Layer createLayer(String tableName, JSONObject layerJSON) throws ServiceException {
Layer lyr = null;
try {
GeoJSONParser.ShapeType shapeType = GeoJSONParser.getShape(layerJSON);
int srid = GeoJSONParser.getSridInt(layerJSON);
String schemaTable = SCHEMA + ".\"" + tableName + "\"";
Statement stmt = conn.createStatement();
if (!exists(SCHEMA, tableName)) {
String sql = "CREATE TABLE " + schemaTable + " (id serial)";
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
sql = "SELECT AddGeometryColumn ('" +
SCHEMA + "','" + tableName + "', 'the_geom', " +
String.valueOf(srid) + ", '" + shapeType + "', 2);";
LOG.log(Level.INFO, sql);
stmt.executeQuery(sql);
}
lyr = new Layer(SCHEMA, tableName, layerJSON);
stmt.close();
} catch (JSONException|SQLException ex) {
throw new csip.ServiceException(ex);
}
return lyr;
}
@Override
public Feature uploadFeature(JSONObject featureJSON, Layer layer) throws csip.ServiceException {
try {
Statement stmt = conn.createStatement();
String sql = "SELECT ST_GeomFromGeoJSON('" + featureJSON.getJSONObject("geometry").toString() + "') 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(SCHEMA, 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(SCHEMA, layer.table, key, DataType.STRING);
}
else if (props.get(key) instanceof Double ||
props.get(key) instanceof Integer ||
props.get(key) instanceof Number) {
addColumn(SCHEMA, 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.ServiceException(ex);
}
}
@Override
public int deleteTable(String tableName) throws ServiceException {
try {
Statement stmt = conn.createStatement();
String sql = "DROP TABLE IF EXISTS " + tableName;
LOG.log(Level.INFO, sql);
int ret = stmt.executeUpdate(sql);
stmt.close();
return ret;
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public int deleteLayer(Layer lyr) throws ServiceException {
return deleteTable(lyr.getSchemaTable());
}
@Override
public Layer intersect(Layer one, Layer two) throws ServiceException {
try {
String table = "intsn_" + SESSION_ID + "_" + new RandomString(4).nextString();
String schemaTable = SCHEMA + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
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);
stmt.close();
return new Layer(SCHEMA, table, null);
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public Layer union(Layer one, Layer two) throws ServiceException {
try {
String table = "union_" + SESSION_ID + "_" + new RandomString(4).nextString();
String schemaTable = SCHEMA + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(SCHEMA);
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);
stmt.close();
return new Layer(SCHEMA, table, null);
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public Layer buffer(Layer lyr, int distance) throws ServiceException {
try {
String table = "buf_" + SESSION_ID + "_" + new RandomString(4).nextString();
String schemaTable = SCHEMA + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(SCHEMA);
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);
stmt.close();
return new Layer(SCHEMA, table, null);
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public Layer minimumBoundingCircle(Layer lyr, int numSegsPerQtCirc) throws ServiceException {
try {
String table = "mbc_" + SESSION_ID + "_" + new RandomString(4).nextString();
String schemaTable = SCHEMA + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(SCHEMA);
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);
stmt.close();
return new Layer(SCHEMA, table, null);
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public Layer minimumBoundingRectangle(Layer lyr) throws ServiceException {
try {
String table = "mbr_" + SESSION_ID + "_" + new RandomString(4).nextString();
String schemaTable = SCHEMA + ".\"" + 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);
stmt.close();
return new Layer(SCHEMA, table, null);
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public Layer extent(Layer lyr) throws ServiceException {
try {
String table = "ext_" + SESSION_ID + "_" + new RandomString(4).nextString();
String schemaTable = SCHEMA + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(SCHEMA);
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);
stmt.close();
return new Layer(SCHEMA, table, null);
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public Layer envelope(Layer lyr) throws ServiceException {
try {
String table = "env_" + SESSION_ID + "_" + new RandomString(4).nextString();
String schemaTable = SCHEMA + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(SCHEMA);
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);
stmt.close();
return new Layer(SCHEMA, table, null);
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public Layer area(Layer lyr) throws ServiceException {
try {
String table = "area_" + SESSION_ID + "_" + new RandomString(4).nextString();
String schemaTable = SCHEMA + ".\"" + table + "\"";
Statement stmt = conn.createStatement();
createSchema(SCHEMA);
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);
stmt.close();
return new Layer(SCHEMA, table, null);
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
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")) {
jsonProps.put(col, rs.getObject(col));
}
}
feat.put("properties", jsonProps);
features.put(feat);
}
stmt.close();
featureCollection.put("features", features);
return featureCollection;
} catch (SQLException|JSONException ex) {
throw new csip.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.ServiceException(ex);
}
return wkt;
}
@Override
public boolean exists(String schema, String tableName) throws ServiceException {
try {
Statement stmt = conn.createStatement();
String sql = "SELECT EXISTS ( " +
"SELECT 1 " +
"FROM pg_catalog.pg_class c " +
"JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
"WHERE n.nspname = '" + schema + "' " +
"AND c.relname = '" + tableName + "' " +
")";
LOG.log(Level.INFO, sql);
ResultSet rs = stmt.executeQuery(sql);
rs.next();
boolean exists = rs.getBoolean("exists");
stmt.close();
return exists;
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public void addColumn(String schema, String tableName, String columnName, DataType type) throws ServiceException {
try {
String sql = "ALTER TABLE " + schema + ".\"" + tableName + "\" ADD COLUMN " + columnName + " ";
switch (type) {
case NUMERIC:
sql += "numeric";
break;
case STRING:
sql += "character varying";
break;
default:
sql += "character varying";
break;
}
Statement stmt = conn.createStatement();
LOG.log(Level.INFO, sql);
stmt.executeUpdate(sql);
stmt.close();
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
}
@Override
public ArrayList<String> getColumns(String schema, String tableName) throws ServiceException {
ArrayList<String> cols = new ArrayList<>();
try {
Statement stmt = conn.createStatement();
String sql = "SELECT attrelid::regclass, attnum, attname " +
"FROM pg_attribute " +
"WHERE attrelid = '" + schema + ".\"" + tableName + "\"'::regclass " +
"AND attnum > 0 " +
"AND NOT attisdropped " +
"ORDER BY attnum";
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
cols.add(rs.getString("attname"));
}
stmt.close();
} catch (SQLException ex) {
throw new csip.ServiceException(ex);
}
return cols;
}
public String getProperties(Layer lyr, boolean useTableSuffix) throws ServiceException {
ArrayList<String> colsExclude = new ArrayList<>(Arrays.asList(new String[]{
"the_geom",
"id",
}));
return getProperties(lyr, useTableSuffix, colsExclude);
}
public String getProperties(Layer lyr, boolean useTableSuffix, ArrayList<String> colsExclude) throws ServiceException {
ArrayList<String> props = new ArrayList<>();
ArrayList<String> cols = getColumns(lyr.schema, lyr.table);
for (String col : cols) {
if (!colsExclude.contains(col)) {
String prop = lyr.getSchemaTable() + "." + col + " AS " + col;
if (useTableSuffix && hasSuffix(lyr)) {
prop += "_" + lyr.table.charAt(lyr.table.length() - 1);
}
props.add(prop);
}
}
return LocalStringUtils.join(props, ",");
}
public boolean hasSuffix(Layer lyr) {
return RegularExpression.matches("_[0-9]$", lyr.table);
}
}
/**
* Interfaces with the Java Topology Library
* @author ktraff
*/
class JTSAdapter {
protected final Logger LOG = Logger.getLogger(getClass().getSimpleName());
Database db;
public JTSAdapter(Database 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);
}
}
}