/*---------------- 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
********************************************************************** */