/* ***********************************************************************
* This layer is for the reading and display of any spatial data
* retrieved from a PostGIS Database.
* The code is heavily copied by the MySQL layer and the PostGIS JDBC Driver
*
* Properties to be set:
*
*
* prettyName=<Your Layer Name>
* dbUrl=< Driver Class > eg. "jdbc:postgresql_postGIS://localhost/postgres?user=user&password=password"
* dbClass=< Driver Class > eg. "org.postgis.DriverWrapper"
* geomTable=<Database Tablename>
* geomColumn=<Column name which contains the geometry>
* pointSymbol=<Filename and path for image to use for point objects>Default is
* # Optional Properties - use as required
* # NOTE: There are default for each of these
* lineColor=<Color for lines>Default is red
* lineWidth=<Pixel width of lines>Default is 0
* fillColor=<Color of fill>Default is red
* This program is distributed freely and in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Coded in 2007 by Filippo Di Natale, Milano - Italy
*
* Author name: Filippo Di Natale
*
* ***********************************************************************
*/
package ssi.be.GIS.layers.postgis;
import java.sql.*;
import java.util.Properties;
/* POSTGIS */
import org.postgis.Geometry;
import org.postgis.GeometryCollection;
import org.postgis.LineString;
import org.postgis.LinearRing;
import org.postgis.MultiLineString;
import org.postgis.MultiPoint;
import org.postgis.MultiPolygon;
import org.postgis.Point;
import org.postgis.Polygon;
import org.postgis.binary.ByteGetter;
import org.postgis.binary.ValueGetter;
import org.postgis.binary.ByteGetter.BinaryByteGetter;
import org.postgis.binary.ByteGetter.StringByteGetter;
/* OpenMap */
import com.bbn.openmap.LatLonPoint;
import com.bbn.openmap.layer.OMGraphicHandlerLayer;
import com.bbn.openmap.util.PropUtils;
import com.bbn.openmap.omGraphics.DrawingAttributes;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMPoint;
import com.bbn.openmap.omGraphics.OMPoly;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;
/**
* This layer is for the reading and display of any spatial data
* retrieved from a PostGIS Database.
* Properties to be set:
*
* prettyName=<Your Layer Name>
* dbUrl=< Driver Class > eg. "jdbc:postgresql_postGIS://localhost/postgres?user=user&password=password"
* dbClass=< Driver Class > eg. "org.postgis.DriverWrapper"
* geomTable=<Database Tablename>
* geomColumn=<Column name which contains the geometry>
* pointSymbol=<Filename and path for image to use for point objects>Default is
* # Optional Properties - use as required
* # NOTE: There are default for each of these
* lineColor=<Color for lines>Default is red
* lineWidth=<Pixel width of lines>Default is 0
* fillColor=<Color of fill>Default is red
*
* Example Usage:
*
* PostGISGeometryLayer postgisLayer = new PostGISGeometryLayer();
*
* Properties postgisLayerProps = new Properties();
* postgisLayerProps.put("prettyName", "Province");
* postgisLayerProps.put("dbUrl", "jdbc:postgresql_postGIS://localhost/postgres?user=user&password=password");
* postgisLayerProps.put("dbClass", "org.postgis.DriverWrapper");
* postgisLayerProps.put("geomTable", "my_spatial_table");
* postgisLayerProps.put("geomColumn", "the_geom");
* postgisLayer.setProperties(postgisLayerProps);
* postgisLayer.setVisible(true);
* mapHandler.add(postgisLayer);
*
*
*
*/
public class PostGISGeometryLayer extends OMGraphicHandlerLayer
{
private static final long serialVersionUID = 1L;
protected String debugQuery = null;
/**
* ; The connection String to use for the jdbc query, e.g.
* "jdbc:mysql://localhost/openmap?user=me&password=secret"
*/
protected String dbUrl = null;
/**
* The Property to set for the query: dbUrl .
*/
public static final String dbUrlProperty = "dbUrl";
/**
* ; The driver to use.
*/
protected String dbClass = null;
/**
* The property to use for specifing the driver: dbClass
*/
public static final String dbClassProperty = "dbClass";
/**
* Connection to server.
*/
protected Connection conn = null;
/**
* The result set object.
*/
protected ResultSet rs = null;
/**
* Statement to be executed for queries.
*/
protected Statement stmt = null;
/** Table name which contains the geometry to be used. */
protected String geomTable = null;
/**
* Property to specify geomTable in the Database: geomTable
* .
*/
public static final String geomTableProperty = "geomTable";
/** Column name which contains the geometry to be used. */
protected String geomColumn = null;
/**
* Property to specify geomColumn in the Database: geomColumn
*
*/
public static final String geomColumnProperty = "geomColumn";
/** The point Symbol set by the Properties */
protected String pointSymbol = "";
/**
* Property to specify GIF or image file(symbol) to use for
* Points: pointSymbol .
*/
public static final String pointSymbolProperty = "pointSymbol";
protected DrawingAttributes drawingAttributes = DrawingAttributes.getDefaultClone();
/**
* The properties and prefix are managed and decoded here.
*
* @param prefix string prefix used in the properties file for
* this layer.
* @param properties the properties set in the properties file.
*/
public void setProperties(String prefix, Properties properties) {
super.setProperties(prefix, properties);
prefix = PropUtils.getScopedPropertyPrefix(prefix);
dbClass = properties.getProperty(prefix + dbClassProperty);
dbUrl = properties.getProperty(prefix + dbUrlProperty);
geomTable = properties.getProperty(prefix + geomTableProperty);
geomColumn = properties.getProperty(prefix + geomColumnProperty);
pointSymbol = properties.getProperty(prefix + pointSymbolProperty);
if (Debug.debugging("postgis")) {
Debug.output("PostGISGeometryLayer (" + getName() + ") properties:");
Debug.output(" " + dbClass);
Debug.output(" " + dbUrl);
Debug.output(" " + geomTable);
Debug.output(" " + geomColumn);
}
drawingAttributes.setProperties(prefix, properties);
}
public synchronized OMGraphicList prepare() {
Projection proj = getProjection();
LatLonPoint upperLeft = proj.getUpperLeft();
LatLonPoint lowerRight = proj.getLowerRight();
if (proj == null) {
Debug.output("PostGISGeometryLayer.prepare: null projection!");
return null;
}
OMGraphicList graphics = new OMGraphicList();
try {
Class.forName(dbClass).newInstance();
try {
conn = DriverManager.getConnection(dbUrl);
} catch (Exception ex) {
ex.printStackTrace();
}
stmt = conn.createStatement();
String geomString = "SetSrid('BOX3D("+
upperLeft.getLongitude()+" "+upperLeft.getLatitude()+","+
lowerRight.getLongitude()+" "+lowerRight.getLatitude()+
")'::box3d,4326)";
//The simplify function speeds up the display, would be nice to find an
//algorithm to calculate the right value for the parameter (now I slammed in
//0.0005 without much rational backing)
String query = "select AsBinary(simplify(" + geomColumn + ",0.0005)) from " +
geomTable +
" where (" + geomColumn + " && " + geomString + " = true)";
//Useful to draw elements using WKT format for testing purposes without using
//a real existing table
if (debugQuery != null)
query = debugQuery;
stmt.executeQuery(query);
rs = stmt.getResultSet();
graphics.clear();
while (rs.next())
{
byte[] result = rs.getBytes(1);
if (Debug.debugging("PostGIS")) {
Debug.output("PostGISGeometryLayer result: " + result);
}
OMGraphic omg = trasformGeometryToOM(parse(result));
omg.generate(proj);
graphics.add(omg);
}
rs.close();
conn.close();
} catch (SQLException sqlE) {
sqlE.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return graphics;
}
//From here I shamelessly copied from JDBC Postgis Driver source code
/**
* Get the appropriate ValueGetter for my endianness
*
* @param bytes The appropriate Byte Getter
*
* @return the ValueGetter
*/
public static ValueGetter valueGetterForEndian(ByteGetter bytes) {
if (bytes.get(0) == ValueGetter.XDR.NUMBER) { // XDR
return new ValueGetter.XDR(bytes);
} else if (bytes.get(0) == ValueGetter.NDR.NUMBER) {
return new ValueGetter.NDR(bytes);
} else {
throw new IllegalArgumentException("Unknown Endian type:" + bytes.get(0));
}
}
/**
* Parse a hex encoded geometry
*
* Is synchronized to protect offset counter. (Unfortunately, Java does not
* have neither call by reference nor multiple return values.)
*/
public synchronized Geometry parse(String value) {
StringByteGetter bytes = new ByteGetter.StringByteGetter(value);
return parseGeometry(valueGetterForEndian(bytes));
}
/**
* Parse a binary encoded geometry.
*
* Is synchronized to protect offset counter. (Unfortunately, Java does not
* have neither call by reference nor multiple return values.)
*/
public synchronized Geometry parse(byte[] value) {
BinaryByteGetter bytes = new ByteGetter.BinaryByteGetter(value);
return parseGeometry(valueGetterForEndian(bytes));
}
/** Parse a geometry starting at offset. */
protected Geometry parseGeometry(ValueGetter data)
{
byte endian = data.getByte(); // skip and test endian flag
if (endian != data.endian) {
throw new IllegalArgumentException("Endian inconsistency!");
}
int typeword = data.getInt();
int realtype = typeword & 0x1FFFFFFF; // cut off high flag bits
boolean haveZ = (typeword & 0x80000000) != 0;
boolean haveM = (typeword & 0x40000000) != 0;
boolean haveS = (typeword & 0x20000000) != 0;
int srid = -1;
if (haveS) {
srid = data.getInt();
}
Geometry result1;
switch (realtype) {
case Geometry.POINT :
result1 = parsePoint(data, haveZ, haveM);
break;
case Geometry.LINESTRING :
result1 = parseLineString(data, haveZ, haveM);
break;
case Geometry.POLYGON :
result1 = parsePolygon(data, haveZ, haveM);
break;
case Geometry.MULTIPOINT :
result1 = parseMultiPoint(data);
break;
case Geometry.MULTILINESTRING :
result1 = parseMultiLineString(data);
break;
case Geometry.MULTIPOLYGON :
result1 = parseMultiPolygon(data);
break;
case Geometry.GEOMETRYCOLLECTION :
result1 = parseCollection(data);
break;
default :
throw new IllegalArgumentException("Unknown Geometry Type: " + realtype);
}
Geometry result = result1;
if (haveS) {
result.setSrid(srid);
}
return result;
}
private Point parsePoint(ValueGetter data, boolean haveZ, boolean haveM) {
double X = data.getDouble();
double Y = data.getDouble();
Point result;
if (haveZ) {
double Z = data.getDouble();
result = new Point(X, Y, Z);
} else {
result = new Point(X, Y);
}
if (haveM) {
result.setM(data.getDouble());
}
return result;
}
/** Parse an Array of "full" Geometries */
private void parseGeometryArray(ValueGetter data, Geometry[] container) {
for (int i = 0; i < container.length; i++) {
container[i] = parseGeometry(data);
}
}
/**
* Parse an Array of "slim" Points (without endianness and type, part of
* LinearRing and Linestring, but not MultiPoint!
*
* @param haveZ
* @param haveM
*/
private Point[] parsePointArray(ValueGetter data, boolean haveZ, boolean haveM) {
int count = data.getInt();
Point[] result = new Point[count];
for (int i = 0; i < count; i++) {
result[i] = parsePoint(data, haveZ, haveM);
}
return result;
}
private MultiPoint parseMultiPoint(ValueGetter data) {
Point[] points = new Point[data.getInt()];
parseGeometryArray(data, points);
return new MultiPoint(points);
}
private LineString parseLineString(ValueGetter data, boolean haveZ, boolean haveM) {
Point[] points = parsePointArray(data, haveZ, haveM);
return new LineString(points);
}
private LinearRing parseLinearRing(ValueGetter data, boolean haveZ, boolean haveM) {
Point[] points = parsePointArray(data, haveZ, haveM);
return new LinearRing(points);
}
private Polygon parsePolygon(ValueGetter data, boolean haveZ, boolean haveM) {
int count = data.getInt();
LinearRing[] rings = new LinearRing[count];
for (int i = 0; i < count; i++) {
rings[i] = parseLinearRing(data, haveZ, haveM);
}
return new Polygon(rings);
}
private MultiLineString parseMultiLineString(ValueGetter data) {
int count = data.getInt();
LineString[] strings = new LineString[count];
parseGeometryArray(data, strings);
return new MultiLineString(strings);
}
private MultiPolygon parseMultiPolygon(ValueGetter data) {
int count = data.getInt();
Polygon[] polys = new Polygon[count];
parseGeometryArray(data, polys);
return new MultiPolygon(polys);
}
private GeometryCollection parseCollection(ValueGetter data) {
int count = data.getInt();
Geometry[] geoms = new Geometry[count];
parseGeometryArray(data, geoms);
return new GeometryCollection(geoms);
}
//From here I map the Postgis primitives on OpenMap primitives
//Maybe I make some mistake here?
//*******************OPENMAP***************************************************
private OMGraphic trasformGeometryToOM(Geometry geometry)
{
int type = geometry.getType();
switch (type)
{
case Geometry.POINT :
return transformPointToOM((Point)geometry);
case Geometry.LINESTRING :
return trasformLineStringToOM((LineString)geometry);
case Geometry.POLYGON :
return trasformPolygonToOM((Polygon)geometry);
case Geometry.MULTIPOINT :
return trasformMultiPointToOM((MultiPoint)geometry);
case Geometry.MULTILINESTRING :
return trasformMultiLineStringToOM((MultiLineString)geometry);
case Geometry.MULTIPOLYGON :
return trasformMultiPolygonToOM((MultiPolygon)geometry);
case Geometry.GEOMETRYCOLLECTION :
return trasformGeometryCollectionToOM((GeometryCollection)geometry);
default :
throw new IllegalArgumentException("Unknown Geometry Type: " + type);
}
}
private OMGraphicList trasformGeometryCollectionToOM(GeometryCollection collection)
{
Geometry[] geometries = collection.getGeometries();
OMGraphicList list = new OMGraphicList(geometries.length);
for (int k = 0; k