/* *********************************************************************** * 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=&ltYour Layer Name&gt * dbUrl=&lt Driver Class &gt eg. "jdbc:postgresql_postGIS://localhost/postgres?user=user&password=password" * dbClass=&lt Driver Class &gt eg. "org.postgis.DriverWrapper" * geomTable=&ltDatabase Tablename&gt * geomColumn=&ltColumn name which contains the geometry&gt * pointSymbol=&ltFilename and path for image to use for point objects&gtDefault is * # Optional Properties - use as required * # NOTE: There are default for each of these * lineColor=&ltColor for lines&gtDefault is red * lineWidth=&ltPixel width of lines&gtDefault is 0 * fillColor=&ltColor of fill&gtDefault 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=&ltYour Layer Name&gt * dbUrl=&lt Driver Class &gt eg. "jdbc:postgresql_postGIS://localhost/postgres?user=user&password=password" * dbClass=&lt Driver Class &gt eg. "org.postgis.DriverWrapper" * geomTable=&ltDatabase Tablename&gt * geomColumn=&ltColumn name which contains the geometry&gt * pointSymbol=&ltFilename and path for image to use for point objects&gtDefault is * # Optional Properties - use as required * # NOTE: There are default for each of these * lineColor=&ltColor for lines&gtDefault is red * lineWidth=&ltPixel width of lines&gtDefault is 0 * fillColor=&ltColor of fill&gtDefault 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