/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed 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. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53115 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.io.sdeapi; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.Vector; import org.deegree.datatypes.Types; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.StringTools; import org.deegree.model.spatialschema.Curve; import org.deegree.model.spatialschema.Envelope; import org.deegree.model.spatialschema.Geometry; import org.deegree.model.spatialschema.GeometryFactory; import org.deegree.model.spatialschema.MultiCurve; import org.deegree.model.spatialschema.MultiPoint; import org.deegree.model.spatialschema.MultiSurface; import org.deegree.model.spatialschema.Point; import org.deegree.model.spatialschema.Position; import org.deegree.model.spatialschema.Surface; import org.deegree.model.spatialschema.SurfaceInterpolation; import org.deegree.model.spatialschema.SurfaceInterpolationImpl; import org.deegree.model.table.DefaultTable; import org.deegree.model.table.Table; import org.deegree.model.table.TableException; import com.esri.sde.sdk.client.SDEPoint; import com.esri.sde.sdk.client.SeColumnDefinition; import com.esri.sde.sdk.client.SeConnection; import com.esri.sde.sdk.client.SeException; import com.esri.sde.sdk.client.SeExtent; import com.esri.sde.sdk.client.SeFilter; import com.esri.sde.sdk.client.SeLayer; import com.esri.sde.sdk.client.SeQuery; import com.esri.sde.sdk.client.SeRow; import com.esri.sde.sdk.client.SeShape; import com.esri.sde.sdk.client.SeShapeFilter; import com.esri.sde.sdk.client.SeSqlConstruct; import com.esri.sde.sdk.client.SeTable; import com.esri.sde.sdk.client.SeWarningException; /** * This class handles a complete ArcSDE request: * If instanciated, the class can open a connection/instance of the specified * ArcSDE server, set a bounding box as a spatial filter to query the defined * layer. The resultset of the query contains the geometries as well as the * tabular data associated with them. The table is stored as a deegree Table * object whereas the geometries are stored as an array of deegree GM_Objects. * Depending on the datatype of the geometries, the array of GM_Objects might * be GM_Point, GM_Curve etc. * <p> * Some bits of sample code to create a query: * <p> * <code> * SpatialQuery sq = new SpatialQuery();<br> * try {<br> *  sq.openConnection(server, instance, database, user, password);<br> *  sq.setLayer(layer);<br> *  sq.setSpatialFilter(minX, minY, maxX, maxY);<br> *  sp.runSpatialQuery();<br> *  GM_Object[] deegree_gm_obj = sq.getGeometries();<br> *  Table deegree_table = sq.getTable();<br> *  sq.closeConnection();<br> * } catch ( SeException sexp ) {<br> * }<br> * </code> * @author <a href="mailto:bedel@giub.uni-bonn.de">Markus Bedel</a> * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> * @version $Revision: 1.15 $ $Date: 2006/11/27 09:07:52 $ */ public class SpatialQuery { private static final ILogger LOG = LoggerFactory.getLogger( SpatialQuery.class ); // Connection to SDE private SeConnection conn = null; // Currently opened Layer and associated Table private SeLayer layer = null; // Current Spatial Filter - a BoundingBox private SeShape spatialFilter = null; private SeTable table = null; // The Query ResultObjects private Geometry[] deegreeGeom = null; /** * Creates a new SpatialQuery object. * * @param server * @param port * @param database * @param user * @param password * * @throws SeException */ public SpatialQuery( String server, int port, String database, String user, String password ) throws SeException { openConnection( server, port, database, user, password ); } /** * Connect to the ArcSDE server * <br>throws SeException */ public void openConnection( String server, int port, String database, String user, String password ) throws SeException { conn = new SeConnection( server, port, database, user, password ); } /** * Close the current connection to the ArcSDE server * <br>throws SeException */ public void closeConnection() throws SeException { conn.close(); } /** * Set a SDE layer to work on and appropriate table * <br>throws SeException */ public void setLayer( String layername ) throws SeException { Vector layerList = conn.getLayers(); String spatialCol = null; for ( int i = 0; i < layerList.size(); i++ ) { SeLayer layer = (SeLayer) layerList.elementAt( i ); if ( layer.getQualifiedName().trim().equalsIgnoreCase( layername ) ) { spatialCol = layer.getSpatialColumn(); break; } } layer = new SeLayer( conn, layername, spatialCol ); table = new SeTable( conn, layer.getQualifiedName() ); } /** * Get the current SDE layer * <br>returns null if it not yet set. */ public SeLayer getLayer() { return layer; } /** * Set a SpatialFilter to Query (BoundingBox) * <br>throws SeException */ public void setSpatialFilter( double minx, double miny, double maxx, double maxy ) throws SeException { Envelope layerBBox = GeometryFactory.createEnvelope( layer.getExtent().getMinX(), layer.getExtent().getMinY(), layer.getExtent().getMaxX(), layer.getExtent().getMaxY(), null ); Envelope query = GeometryFactory.createEnvelope( minx, miny, maxx, maxy, null ); query = query.createIntersection( layerBBox ); if ( query != null ) { spatialFilter = new SeShape( layer.getCoordRef() ); SeExtent extent = new SeExtent( query.getMin().getX(), query.getMin().getY(), query.getMax().getX(), query.getMax().getY() ); spatialFilter.generateRectangle( extent ); } else { spatialFilter = null; } } /** * Get the current Spatial Filter * <br>returns null if it not yet set. */ public SeShape getSpatialFilter() { return spatialFilter; } /** * Get GM_Object[] containing the queried Geometries * <br>returns null if no query has been done yet. */ public Geometry[] getGeometries() { return deegreeGeom; } /** * Runs a spatial query against the opened layer using the specified spatial filter. * <br>throws SeException */ public Table runSpatialQuery( String[] cols ) throws SeException { Table deegreeTable = null; if ( spatialFilter != null ) { SeShapeFilter[] filters = new SeShapeFilter[1]; filters[0] = new SeShapeFilter( layer.getQualifiedName(), layer.getSpatialColumn(), spatialFilter, SeFilter.METHOD_ENVP ); SeColumnDefinition[] tableDef = table.describe(); if ( cols == null || cols.length == 0 ) { cols = new String[tableDef.length]; for ( int i = 0; i < tableDef.length; i++ ) { cols[i] = tableDef[i].getName(); } } SeSqlConstruct sqlCons = new SeSqlConstruct( layer.getQualifiedName() ); SeQuery spatialQuery = new SeQuery( conn, cols, sqlCons ); spatialQuery.prepareQuery(); spatialQuery.setSpatialConstraints( SeQuery.SE_OPTIMIZE, false, filters ); spatialQuery.execute(); SeRow row = spatialQuery.fetch(); int numRows = 0; if ( row != null ) { int numCols = row.getNumColumns(); // Fetch all the features that satisfied the query deegreeTable = initTable( row ); ArrayList list = new ArrayList( 20000 ); Object[] tableObj = null; while ( row != null ) { int colNum = 0; tableObj = new Object[deegreeTable.getColumnCount()]; for ( int i = 0; i < numCols; i++ ) { SeColumnDefinition colDef = row.getColumnDef( i ); if ( row.getIndicator( (short) i ) != SeRow.SE_IS_NULL_VALUE ) { switch ( colDef.getType() ) { case SeColumnDefinition.TYPE_SMALLINT: tableObj[colNum++] = row.getShort( i ); break; case SeColumnDefinition.TYPE_INTEGER: tableObj[colNum++] = row.getInteger( i ); break; case SeColumnDefinition.TYPE_FLOAT: tableObj[colNum++] = row.getFloat( i ); break; case SeColumnDefinition.TYPE_DOUBLE: tableObj[colNum++] = row.getDouble( i ); break; case SeColumnDefinition.TYPE_STRING: tableObj[colNum++] = row.getString( i ); break; case SeColumnDefinition.TYPE_BLOB: ByteArrayInputStream bis = (ByteArrayInputStream) row.getObject( i ); tableObj[colNum++] = bis; break; case SeColumnDefinition.TYPE_DATE: tableObj[colNum++] = row.getDate( i ); break; case SeColumnDefinition.TYPE_RASTER: LOG.logInfo( colDef.getName() + " : Cant handle this" ); break; case SeColumnDefinition.TYPE_SHAPE: SeShape spVal = row.getShape( i ); createGeometry( spVal, list ); break; default: LOG.logInfo( "Unknown Table DataType" ); break; } // End switch(type) } // End if } // End for numRows++; try { deegreeTable.appendRow( tableObj ); } catch ( TableException tex ) { throw new SeWarningException( tex.toString() ); } row = spatialQuery.fetch(); } // End while spatialQuery.close(); deegreeGeom = new Geometry[list.size()]; deegreeGeom = (Geometry[]) list.toArray( deegreeGeom ); } else { try { deegreeTable = new DefaultTable( layer.getQualifiedName(), new String[] { "NONE" }, new int[] { Types.VARCHAR }, 2 ); } catch ( Exception e ) { e.printStackTrace(); } deegreeGeom = new Geometry[0]; } } else { try { deegreeTable = new DefaultTable( layer.getQualifiedName(), new String[] { "NONE" }, new int[] { Types.VARCHAR }, 2 ); } catch ( Exception e ) { e.printStackTrace(); } deegreeGeom = new Geometry[0]; } return deegreeTable; } // End method runSpatialQuery /** * Initialize Table object - used with first row of the SpatialQuery * This method sets the TableName, TableColumnNames and their DataTypes * <br>throws SeException */ private Table initTable( SeRow row ) throws SeException { ArrayList colNames = new ArrayList( 50 ); ArrayList colTypes = new ArrayList( 50 ); Table deegreeTable = null; SeColumnDefinition colDef = null; for ( int i = 0; i < row.getNumColumns(); i++ ) { try { colDef = row.getColumnDef( i ); } catch ( SeException sexp ) { sexp.printStackTrace(); throw new SeWarningException( sexp.toString() ); } switch ( colDef.getType() ) { case SeColumnDefinition.TYPE_SMALLINT: colNames.add( colDef.getName().toUpperCase() ); colTypes.add( new Integer( Types.SMALLINT ) ); break; case SeColumnDefinition.TYPE_INTEGER: colNames.add( colDef.getName().toUpperCase() ); colTypes.add( new Integer( Types.INTEGER ) ); break; case SeColumnDefinition.TYPE_FLOAT: colNames.add( colDef.getName().toUpperCase() ); colTypes.add( new Integer( Types.FLOAT ) ); break; case SeColumnDefinition.TYPE_DOUBLE: colNames.add( colDef.getName().toUpperCase() ); colTypes.add( new Integer( Types.DOUBLE ) ); break; case SeColumnDefinition.TYPE_STRING: colNames.add( colDef.getName().toUpperCase() ); colTypes.add( new Integer( Types.VARCHAR ) ); break; case SeColumnDefinition.TYPE_BLOB: // there is an open issue with fetching blobs, // look at this document: // "ArcSDE 8.1 Java API - BLOB columns" // http://support.esri.com/Search/KbDocument.asp?dbid=17068 colNames.add( colDef.getName().toUpperCase() ); colTypes.add( new Integer( Types.ARRAY ) ); break; case SeColumnDefinition.TYPE_DATE: colNames.add( colDef.getName().toUpperCase() ); colTypes.add( new Integer( Types.DATE ) ); break; default: break; } } String[] colN = new String[colNames.size()]; colN = (String[]) colNames.toArray( colN ); int[] colT = new int[colTypes.size()]; for ( int i = 0; i < colT.length; i++ ) { colT[i] = ( (Integer) colTypes.get( i ) ).intValue(); } try { deegreeTable = new DefaultTable( layer.getQualifiedName(), colN, colT, 20000 ); } catch ( TableException tex ) { tex.printStackTrace(); throw new SeWarningException( tex.toString() ); } return deegreeTable; } // End Method initTable /** * CreateGeometry - used with every row of the SpatialQuery * Depending on the layers' geometries datatype different operations * are made to create the appropriate object. *<br>Available ArcSDE ShapeTypes: *<br> TYPE_POINT (impl) *<br> TYPE_MULTI_POINT (impl) *<br> TYPE_SIMPLE_LINE (impl) *<br> TYPE_MULTI_SIMPLE_LINE (impl) *<br> TYPE_LINE (impl) *<br> TYPE_MULTI_LINE (impl) *<br> TYPE_POLYGON (impl) *<br> TYPE_MULTI_POLYGON (impl) *<br> TYPE_NIL (impl) * *<br>throws SeException */ private void createGeometry( SeShape shape, ArrayList list ) throws SeException { int shptype = shape.getType(); ArrayList al = shape.getAllPoints( SeShape.TURN_DEFAULT, true ); // Retrieve the array of SDEPoints SDEPoint[] points = (SDEPoint[]) al.get( 0 ); // Retrieve the part offsets array. int[] partOffset = (int[]) al.get( 1 ); // Retrieve the sub-part offsets array. int[] subPartOffset = (int[]) al.get( 2 ); int numPoints = shape.getNumOfPoints(); int numParts = shape.getNumParts(); switch ( shptype ) { // a single point case SeShape.TYPE_NIL: Point gmPoint = GeometryFactory.createPoint( -9E9, -9E9, null ); list.add( gmPoint ); LOG.logInfo( "Found SeShape.TYPE_NIL." ); LOG.logInfo( "The queried layer does not have valid geometries" ); break; // a single point case SeShape.TYPE_POINT: gmPoint = GeometryFactory.createPoint( points[0].getX(), points[0].getY(), null ); list.add( gmPoint ); break; // an array of points case SeShape.TYPE_MULTI_POINT: Point[] gmPoints = new Point[numPoints]; for ( int pt = 0; pt < numPoints; pt++ ) { gmPoints[pt] = GeometryFactory.createPoint( points[pt].getX(), points[pt].getY(), null ); } try { MultiPoint gmMultiPoint = GeometryFactory.createMultiPoint( gmPoints ); list.add( gmMultiPoint ); } catch ( Exception gme ) { gme.printStackTrace(); throw new SeWarningException( gme.toString() ); } break; // a single line, simple as it does not intersect itself case SeShape.TYPE_SIMPLE_LINE: // or a single, non-simple line case SeShape.TYPE_LINE: Position[] gmSimpleLinePosition = new Position[numPoints]; for ( int pt = 0; pt < numPoints; pt++ ) { gmSimpleLinePosition[pt] = GeometryFactory.createPosition( points[pt].getX(), points[pt].getY() ); } try { Curve gmCurve = GeometryFactory.createCurve( gmSimpleLinePosition, null ); list.add( gmCurve ); } catch ( Exception gme ) { gme.printStackTrace(); throw new SeWarningException( gme.toString() ); } break; // an array of lines, simple as they do not intersect with themself case SeShape.TYPE_MULTI_SIMPLE_LINE: // or an array of non-simple lines case SeShape.TYPE_MULTI_LINE: Curve[] gmCurves = new Curve[numParts]; for ( int partNo = 0; partNo < numParts; partNo++ ) { int lastPoint = shape.getNumPoints( partNo + 1, 1 ) + partOffset[partNo]; Position[] gmMultiSimpleLinePosition = new Position[shape.getNumPoints( partNo + 1, 1 )]; int i = 0; for ( int pt = partOffset[partNo]; pt < lastPoint; pt++ ) { gmMultiSimpleLinePosition[i] = GeometryFactory.createPosition( points[pt].getX(), points[pt].getY() ); i++; } try { gmCurves[partNo] = GeometryFactory.createCurve( gmMultiSimpleLinePosition, null ); } catch ( Exception gme ) { gme.printStackTrace(); throw new SeWarningException( gme.toString() ); } } try { MultiCurve gmMultiCurve = GeometryFactory.createMultiCurve( gmCurves ); list.add( gmMultiCurve ); } catch ( Exception gme ) { gme.printStackTrace(); throw new SeWarningException( gme.toString() ); } break; // a single polygon which might contain islands case SeShape.TYPE_POLYGON: int numSubParts = shape.getNumSubParts( 1 ); Position[] gmPolygonExteriorRing = new Position[shape.getNumPoints( 1, 1 )]; int kk = shape.getNumPoints( 1, 1 ); for ( int pt = 0; pt < kk; pt++ ) { gmPolygonExteriorRing[pt] = GeometryFactory.createPosition( points[pt].getX(), points[pt].getY() ); } Position[][] gmPolygonInteriorRings = null; // if it is a donut create inner rings if ( numSubParts > 1 ) { gmPolygonInteriorRings = new Position[numSubParts - 1][]; int j = 0; for ( int subPartNo = 1; subPartNo < numSubParts; subPartNo++ ) { int lastPoint = shape.getNumPoints( 1, subPartNo + 1 ) + subPartOffset[subPartNo]; Position[] gmPolygonPosition = new Position[shape.getNumPoints( 1, subPartNo + 1 )]; int i = 0; for ( int pt = subPartOffset[subPartNo]; pt < lastPoint; pt++ ) { gmPolygonPosition[i] = GeometryFactory.createPosition( points[pt].getX(), points[pt].getY() ); i++; } gmPolygonInteriorRings[j] = gmPolygonPosition; j++; } } try { Surface gmSurface = GeometryFactory.createSurface( gmPolygonExteriorRing, gmPolygonInteriorRings, new SurfaceInterpolationImpl(), null ); list.add( gmSurface ); } catch ( Exception gme ) { gme.printStackTrace(); throw new SeWarningException( gme.toString() ); } break; // an array of polygons which might contain islands case SeShape.TYPE_MULTI_POLYGON: Surface[] gmMultiPolygonSurface = getMultiPolygon( shape, points, partOffset, subPartOffset ); try { MultiSurface gmMultiSurface = GeometryFactory.createMultiSurface( gmMultiPolygonSurface ); list.add( gmMultiSurface ); } catch ( Exception gme ) { gme.printStackTrace(); throw new SeWarningException( gme.toString() ); } break; default: LOG.logInfo( "Unknown GeometryType - ID: " + shape.getType() ); break; } // End of switch } // End Method createGeometry /** * @param shape * @param points * @param partOffset * @param subPartOffset * @param numParts * @return * @throws SeException * @throws SeWarningException */ private Surface[] getMultiPolygon( SeShape shape, SDEPoint[] points, int[] partOffset, int[] subPartOffset ) throws SeException, SeWarningException { Surface[] surfaces = new Surface[partOffset.length]; int hh = 0; for ( int i = 0; i < partOffset.length; i++ ) { // cnt = number of all rings of the current polygon (part) int cnt = shape.getNumSubParts( i + 1 ); // exterior ring int count = shape.getNumPoints( i + 1, 1 ); Position[] ex = new Position[count]; int off = subPartOffset[hh]; for ( int j = 0; j < count; j++ ) { ex[j] = GeometryFactory.createPosition( points[j + off].getX(), points[j + off].getY() ); } // interior ring Position[][] inn = null; if ( cnt > 1 ) { inn = new Position[cnt - 1][]; } hh++; for ( int j = 1; j < cnt; j++ ) { inn[j - 1] = new Position[shape.getNumPoints( i + 1, j + 1 )]; off = subPartOffset[hh]; for ( int k = 0; k < inn[j - 1].length; k++ ) { inn[j - 1][k] = GeometryFactory.createPosition( points[j + off - 1].getX(), points[j + off - 1].getY() ); } hh++; } try { SurfaceInterpolation si = new SurfaceInterpolationImpl(); surfaces[i] = GeometryFactory.createSurface( ex, inn, si, null ); } catch ( Exception e ) { new SeWarningException( StringTools.stackTraceToString( e ) ); } } return surfaces; } } // End Class SpatialQueryEx /* ******************************************************************** Changes to this class. What the people have been up to: $Log: SpatialQuery.java,v $ Revision 1.15 2006/11/27 09:07:52 poth JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code. Revision 1.14 2006/08/06 20:59:02 poth never read parameter removed Revision 1.13 2006/07/12 14:46:19 poth comment footer added ********************************************************************** */