//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/datastore/sql/postgis/PostGISWhereBuilder.java,v 1.27 2006/11/29 16:59:54 mschneider Exp $
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
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
Aennchenstraße 19
53177 Bonn
Germany
E-Mail: poth@lat-lon.de
Jens Fitzke
lat/lon GmbH
Aennchenstraße 19
53177 Bonn
Germany
E-Mail: jens.fitzke@uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.io.datastore.sql.postgis;
import java.sql.Types;
import org.deegree.i18n.Messages;
import org.deegree.io.datastore.DatastoreException;
import org.deegree.io.datastore.schema.MappedFeatureType;
import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
import org.deegree.io.datastore.sql.StatementBuffer;
import org.deegree.io.datastore.sql.TableAliasGenerator;
import org.deegree.io.datastore.sql.VirtualContentProvider;
import org.deegree.io.datastore.sql.wherebuilder.WhereBuilder;
import org.deegree.model.filterencoding.Filter;
import org.deegree.model.filterencoding.OperationDefines;
import org.deegree.model.filterencoding.SpatialOperation;
import org.deegree.model.spatialschema.Envelope;
import org.deegree.model.spatialschema.Geometry;
import org.deegree.model.spatialschema.GeometryException;
import org.deegree.ogcbase.SortProperty;
import org.postgis.PGboxbase;
import org.postgis.PGgeometry;
/**
* {@link WhereBuilder} implementation for PostGIS databases.
*
* @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </A>
* @author last edited by: $Author: mschneider $
*
* @version $Revision: 1.27 $, $Date: 2006/11/29 16:59:54 $
*/
class PostGISWhereBuilder extends WhereBuilder {
private PostGISDatastore ds;
/**
* Creates a new instance of <code>PostGISWhereBuilder</code> from the given parameters.
*
* @param ft
* requested feature type
* @param filter
* filter that restricts the matched features
* @param sortProperties
* sort criteria for the result, may be null or empty
* @param aliasGenerator
* used to generate unique table aliases
* @param vcProvider
* @throws DatastoreException
*/
public PostGISWhereBuilder( MappedFeatureType ft, Filter filter, SortProperty[] sortProperties,
TableAliasGenerator aliasGenerator, VirtualContentProvider vcProvider )
throws DatastoreException {
super( ft, filter, sortProperties, aliasGenerator, vcProvider );
this.ds = (PostGISDatastore) ft.getGMLSchema().getDatastore();
}
/**
* Generates an SQL-fragment for the given object.
*
* TODO: Implement BBOX faster using explicit B0X-constructor
*
* @throws DatastoreException
*/
@Override
protected void appendSpatialOperationAsSQL( StatementBuffer query, SpatialOperation operation )
throws DatastoreException {
try {
switch ( operation.getOperatorId() ) {
case OperationDefines.BBOX: {
appendBBOXOperationAsSQL( query, operation );
break;
}
case OperationDefines.INTERSECTS: {
appendIntersectsOperationAsSQL( query, operation );
break;
}
case OperationDefines.CROSSES: {
appendSimpleOperationAsSQL( query, operation, "crosses" );
break;
}
case OperationDefines.EQUALS: {
appendSimpleOperationAsSQL( query, operation, "equals" );
break;
}
case OperationDefines.WITHIN: {
appendSimpleOperationAsSQL( query, operation, "within" );
break;
}
case OperationDefines.OVERLAPS: {
appendSimpleOperationAsSQL( query, operation, "overlaps" );
break;
}
case OperationDefines.TOUCHES: {
appendSimpleOperationAsSQL( query, operation, "touches" );
break;
}
case OperationDefines.DISJOINT: {
appendSimpleOperationAsSQL( query, operation, "disjoint" );
break;
}
case OperationDefines.CONTAINS: {
appendSimpleOperationAsSQL( query, operation, "contains" );
break;
}
case OperationDefines.DWITHIN: {
appendDWithinOperationAsSQL( query, operation );
break;
}
case OperationDefines.BEYOND: {
appendBeyondOperationAsSQL( query, operation );
break;
}
default: {
String msg = "Spatial operator "
+ OperationDefines.getNameById( operation.getOperatorId() )
+ " is not supported by '" + this.getClass().toString() + "'.";
throw new DatastoreException( msg );
}
}
} catch ( GeometryException e ) {
throw new DatastoreException( e );
}
}
private void appendSimpleOperationAsSQL( StatementBuffer query, SpatialOperation operation,
String operationName )
throws GeometryException, DatastoreException {
query.append( operationName );
query.append( "(" );
appendPropertyNameAsSQL( query, operation.getPropertyName() );
query.append( ',' );
appendGeometryArgument( query, this.getGeometryProperty( operation.getPropertyName() ),
operation.getGeometry() );
query.append( ')' );
}
private void appendIntersectsOperationAsSQL( StatementBuffer query, SpatialOperation operation )
throws GeometryException, DatastoreException {
Envelope env = operation.getGeometry().getEnvelope();
MappedGeometryPropertyType geoProperty = this.getGeometryProperty( operation.getPropertyName() );
String argumentSRS = null;
if ( env.getCoordinateSystem() != null ) {
argumentSRS = env.getCoordinateSystem().getAsString();
}
String propertySRS = geoProperty.getCS().getAsString();
int internalSRS = geoProperty.getMappingField().getSRS();
int createSRSCode = getArgumentSRSCode( argumentSRS, propertySRS, internalSRS );
PGboxbase box = PGgeometryAdapter.export( env );
StringBuffer bbox = new StringBuffer( 323 );
bbox.append( "SetSRID(?," + createSRSCode + ")" );
int targetSRSCode = getTargetSRSCode( argumentSRS, propertySRS, internalSRS );
if ( targetSRSCode != SRS_UNDEFINED ) {
bbox = new StringBuffer( this.ds.buildSRSTransformCall( bbox.toString(), targetSRSCode ) );
}
// use the bbox operator (&&) to filter using the spatial index
query.append( "(" );
appendPropertyNameAsSQL( query, operation.getPropertyName() );
query.append( " && " );
query.append( bbox.toString() );
query.addArgument( box, Types.OTHER );
query.append( " AND intersects (" );
appendPropertyNameAsSQL( query, operation.getPropertyName() );
query.append( ',' );
appendGeometryArgument( query, getGeometryProperty( operation.getPropertyName() ),
operation.getGeometry() );
query.append( "))" );
}
private void appendBBOXOperationAsSQL( StatementBuffer query, SpatialOperation operation )
throws DatastoreException, GeometryException {
Envelope env = operation.getGeometry().getEnvelope();
MappedGeometryPropertyType geoProperty = this.getGeometryProperty( operation.getPropertyName() );
String argumentSRS = null;
if ( env.getCoordinateSystem() != null ) {
argumentSRS = env.getCoordinateSystem().getAsString();
}
String propertySRS = geoProperty.getCS().getAsString();
int internalSRS = geoProperty.getMappingField().getSRS();
int createSRSCode = getArgumentSRSCode( argumentSRS, propertySRS, internalSRS );
PGboxbase box = PGgeometryAdapter.export( env );
StringBuffer bbox = new StringBuffer( 323 );
bbox.append( "SetSRID(?," + createSRSCode + ")" );
int targetSRSCode = getTargetSRSCode( argumentSRS, propertySRS, internalSRS );
if ( targetSRSCode != SRS_UNDEFINED ) {
bbox = new StringBuffer( this.ds.buildSRSTransformCall( bbox.toString(), targetSRSCode ) );
}
// only the && operator uses the spatial index
// intersects, contains etc. do not use spatial indexing!!!!
query.append( "(" );
appendPropertyNameAsSQL( query, operation.getPropertyName() );
query.append( " && " );
query.append( bbox.toString() );
query.addArgument( box, Types.OTHER );
// it is necessary to add an explicit intersects as well, because the && operator only
// checks for intersection of the bbox with the bboxes of the geometries (and not the
// geometries themselves)
query.append( " AND intersects (" );
appendPropertyNameAsSQL( query, operation.getPropertyName() );
query.append( ',' );
query.append( bbox.toString() );
query.addArgument( box, Types.OTHER );
query.append( "))" );
}
private void appendDWithinOperationAsSQL( StatementBuffer query, SpatialOperation operation )
throws GeometryException, DatastoreException {
query.append( "distance(" );
appendPropertyNameAsSQL( query, operation.getPropertyName() );
query.append( ',' );
appendGeometryArgument( query, this.getGeometryProperty( operation.getPropertyName() ),
operation.getGeometry() );
query.append( ")<=" );
query.append( "" + operation.getDistance() );
}
private void appendBeyondOperationAsSQL( StatementBuffer query, SpatialOperation operation )
throws GeometryException, DatastoreException {
query.append( "distance(" );
appendPropertyNameAsSQL( query, operation.getPropertyName() );
query.append( ',' );
appendGeometryArgument( query, this.getGeometryProperty( operation.getPropertyName() ),
operation.getGeometry() );
query.append( ")>" );
query.append( "" + operation.getDistance() );
}
/**
* Construct and append the geometry argument using the correct internal SRS and perform a
* transform call to the internal SRS of the {@link MappedGeometryPropertyType} if necessary.
*
* @param query
* @param geoProperty
* @param geometry
* @throws DatastoreException
* @throws GeometryException
*/
private void appendGeometryArgument( StatementBuffer query,
MappedGeometryPropertyType geoProperty, Geometry geometry )
throws DatastoreException, GeometryException {
String argumentSRS = null;
if ( geometry.getCoordinateSystem() != null ) {
argumentSRS = geometry.getCoordinateSystem().getAsString();
}
String propertySRS = geoProperty.getCS().getAsString();
int internalSRS = geoProperty.getMappingField().getSRS();
int createSRSCode = getArgumentSRSCode( argumentSRS, propertySRS, internalSRS );
PGgeometry argument = PGgeometryAdapter.export( geometry, createSRSCode );
int targetSRSCode = getTargetSRSCode( argumentSRS, propertySRS, internalSRS );
if ( targetSRSCode != SRS_UNDEFINED ) {
query.append( ds.buildSRSTransformCall( "?", targetSRSCode ) );
} else {
query.append( '?' );
}
query.addArgument( argument, Types.OTHER );
}
/**
* Returns the internal SRS code that must be used for the creation of a geometry argument
* used in a spatial operator.
*
* @param literalSRS
* @param propertySRS
* @param internalSrs
* @return the internal SRS code that must be used for the creation of a geometry argument
* @throws DatastoreException
*/
private int getArgumentSRSCode( String argumentSRS, String propertySRS, int internalSrs )
throws DatastoreException {
int argumentSRSCode = internalSrs;
if ( argumentSRS == null ) {
argumentSRSCode = internalSrs;
} else if ( !propertySRS.equals( argumentSRS ) ) {
argumentSRSCode = this.ds.getNativeSRSCode( argumentSRS );
if ( argumentSRSCode == SRS_UNDEFINED ) {
String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNKNOWN_SRS",
PostGISDatastore.class.getName(), argumentSRS );
throw new DatastoreException( msg );
}
}
return argumentSRSCode;
}
/**
* Returns the internal SRS code that must be used for the transform call for a geometry
* argument used in a spatial operator.
*
* @param literalSRS
* @param propertySRS
* @param internalSrs
* @return the internal SRS code that must be used for the transform call of a geometry
* argument, or -1 if no transformation is necessary
*/
private int getTargetSRSCode( String argumentSRS, String propertySRS, int internalSrs )
throws DatastoreException {
int targetSRS = SRS_UNDEFINED;
if ( argumentSRS != null && !argumentSRS.equals( propertySRS ) ) {
if ( internalSrs == SRS_UNDEFINED ) {
String msg = Messages.getMessage( "DATASTORE_SRS_NOT_SPECIFIED2", argumentSRS,
propertySRS );
throw new DatastoreException( msg );
}
targetSRS = internalSrs;
}
return targetSRS;
}
}
/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: PostGISWhereBuilder.java,v $
Revision 1.27 2006/11/29 16:59:54 mschneider
Improved handling of native coordinate transformation.
Revision 1.26 2006/11/17 16:58:24 mschneider
Intersects and BBOX use the spatial index correctly now.
Revision 1.25 2006/11/16 10:03:26 poth
*** empty log message ***
Revision 1.24 2006/11/15 18:39:34 mschneider
Cleanup.
Revision 1.23 2006/11/09 17:48:52 mschneider
Implemented native coordinate transformations. Needs testing.
Revision 1.22 2006/09/25 20:31:02 poth
code formating
Revision 1.21 2006/09/19 14:55:16 mschneider
Cleaned up handling of VirtualContent, i.e. properties that are mapped to SQLFunctionCalls.
Revision 1.20 2006/08/28 16:40:33 mschneider
Javadoc fixes.
Revision 1.19 2006/08/24 06:40:05 poth
File header corrected
Revision 1.18 2006/08/14 16:50:43 mschneider
Changed to respect (optional) SortProperties.
Revision 1.17 2006/06/01 12:17:40 mschneider
Fixed header + footer.
********************************************************************** */