001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/datastore/sql/postgis/PostGISWhereBuilder.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 This file is part of deegree. 004 Copyright (C) 2001-2006 by: 005 Department of Geography, University of Bonn 006 http://www.giub.uni-bonn.de/deegree/ 007 lat/lon GmbH 008 http://www.lat-lon.de 009 010 This library is free software; you can redistribute it and/or 011 modify it under the terms of the GNU Lesser General Public 012 License as published by the Free Software Foundation; either 013 version 2.1 of the License, or (at your option) any later version. 014 015 This library is distributed in the hope that it will be useful, 016 but WITHOUT ANY WARRANTY; without even the implied warranty of 017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 018 Lesser General Public License for more details. 019 020 You should have received a copy of the GNU Lesser General Public 021 License along with this library; if not, write to the Free Software 022 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 023 024 Contact: 025 026 Andreas Poth 027 lat/lon GmbH 028 Aennchenstraße 19 029 53177 Bonn 030 Germany 031 E-Mail: poth@lat-lon.de 032 033 Jens Fitzke 034 lat/lon GmbH 035 Aennchenstraße 19 036 53177 Bonn 037 Germany 038 E-Mail: jens.fitzke@uni-bonn.de 039 ---------------------------------------------------------------------------*/ 040 041 package org.deegree.io.datastore.sql.postgis; 042 043 import java.sql.Types; 044 045 import org.deegree.i18n.Messages; 046 import org.deegree.io.datastore.DatastoreException; 047 import org.deegree.io.datastore.schema.MappedFeatureType; 048 import org.deegree.io.datastore.schema.MappedGeometryPropertyType; 049 import org.deegree.io.datastore.sql.StatementBuffer; 050 import org.deegree.io.datastore.sql.TableAliasGenerator; 051 import org.deegree.io.datastore.sql.VirtualContentProvider; 052 import org.deegree.io.datastore.sql.wherebuilder.WhereBuilder; 053 import org.deegree.model.filterencoding.Filter; 054 import org.deegree.model.filterencoding.OperationDefines; 055 import org.deegree.model.filterencoding.SpatialOperation; 056 import org.deegree.model.spatialschema.Envelope; 057 import org.deegree.model.spatialschema.Geometry; 058 import org.deegree.model.spatialschema.GeometryException; 059 import org.deegree.ogcbase.SortProperty; 060 import org.postgis.PGboxbase; 061 import org.postgis.PGgeometry; 062 063 /** 064 * {@link WhereBuilder} implementation for PostGIS databases. 065 * 066 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </A> 067 * @author last edited by: $Author: mschneider $ 068 * 069 * @version $Revision: 6633 $, $Date: 2007-04-18 16:32:16 +0200 (Mi, 18 Apr 2007) $ 070 */ 071 class PostGISWhereBuilder extends WhereBuilder { 072 073 private PostGISDatastore ds; 074 075 /** 076 * Creates a new instance of <code>PostGISWhereBuilder</code> from the given parameters. 077 * 078 * @param rootFts 079 * selected feature types, more than one type means that the types are joined 080 * @param aliases 081 * aliases for the feature types, may be null (must have same length as rootFts 082 * otherwise) 083 * @param filter 084 * filter that restricts the matched features 085 * @param sortProperties 086 * sort criteria for the result, may be null or empty 087 * @param aliasGenerator 088 * used to generate unique table aliases 089 * @param vcProvider 090 * @throws DatastoreException 091 */ 092 public PostGISWhereBuilder( MappedFeatureType[] rootFts, String[] aliases, Filter filter, 093 SortProperty[] sortProperties, TableAliasGenerator aliasGenerator, 094 VirtualContentProvider vcProvider ) throws DatastoreException { 095 super( rootFts, aliases, filter, sortProperties, aliasGenerator, vcProvider ); 096 this.ds = (PostGISDatastore) rootFts[0].getGMLSchema().getDatastore(); 097 } 098 099 /** 100 * Generates an SQL-fragment for the given object. 101 * 102 * TODO: Implement BBOX faster using explicit B0X-constructor 103 * 104 * @throws DatastoreException 105 */ 106 @Override 107 protected void appendSpatialOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 108 throws DatastoreException { 109 110 try { 111 switch ( operation.getOperatorId() ) { 112 case OperationDefines.BBOX: { 113 appendBBOXOperationAsSQL( query, operation ); 114 break; 115 } 116 case OperationDefines.INTERSECTS: { 117 appendIntersectsOperationAsSQL( query, operation ); 118 break; 119 } 120 case OperationDefines.CROSSES: { 121 appendSimpleOperationAsSQL( query, operation, "crosses" ); 122 break; 123 } 124 case OperationDefines.EQUALS: { 125 appendSimpleOperationAsSQL( query, operation, "equals" ); 126 break; 127 } 128 case OperationDefines.WITHIN: { 129 appendSimpleOperationAsSQL( query, operation, "within" ); 130 break; 131 } 132 case OperationDefines.OVERLAPS: { 133 appendSimpleOperationAsSQL( query, operation, "overlaps" ); 134 break; 135 } 136 case OperationDefines.TOUCHES: { 137 appendSimpleOperationAsSQL( query, operation, "touches" ); 138 break; 139 } 140 case OperationDefines.DISJOINT: { 141 appendSimpleOperationAsSQL( query, operation, "disjoint" ); 142 break; 143 } 144 case OperationDefines.CONTAINS: { 145 appendSimpleOperationAsSQL( query, operation, "contains" ); 146 break; 147 } 148 case OperationDefines.DWITHIN: { 149 appendDWithinOperationAsSQL( query, operation ); 150 break; 151 } 152 case OperationDefines.BEYOND: { 153 appendBeyondOperationAsSQL( query, operation ); 154 break; 155 } 156 default: { 157 String msg = "Spatial operator " + OperationDefines.getNameById( operation.getOperatorId() ) 158 + " is not supported by '" + this.getClass().toString() + "'."; 159 throw new DatastoreException( msg ); 160 } 161 } 162 } catch ( GeometryException e ) { 163 throw new DatastoreException( e ); 164 } 165 166 } 167 168 private void appendSimpleOperationAsSQL( StatementBuffer query, SpatialOperation operation, String operationName ) 169 throws GeometryException, DatastoreException { 170 query.append( operationName ); 171 query.append( "(" ); 172 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 173 query.append( ',' ); 174 appendGeometryArgument( query, this.getGeometryProperty( operation.getPropertyName() ), operation.getGeometry() ); 175 query.append( ')' ); 176 } 177 178 private void appendIntersectsOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 179 throws GeometryException, DatastoreException { 180 181 Envelope env = operation.getGeometry().getEnvelope(); 182 MappedGeometryPropertyType geoProperty = this.getGeometryProperty( operation.getPropertyName() ); 183 184 String argumentSRS = null; 185 if ( env.getCoordinateSystem() != null ) { 186 argumentSRS = env.getCoordinateSystem().getName(); 187 } 188 String propertySRS = geoProperty.getCS().getName(); 189 int internalSRS = geoProperty.getMappingField().getSRS(); 190 191 int createSRSCode = getArgumentSRSCode( argumentSRS, propertySRS, internalSRS ); 192 PGboxbase box = PGgeometryAdapter.export( env ); 193 StringBuffer bbox = new StringBuffer( 323 ); 194 bbox.append( "SetSRID(?," + createSRSCode + ")" ); 195 196 int targetSRSCode = getTargetSRSCode( argumentSRS, propertySRS, internalSRS ); 197 if ( targetSRSCode != SRS_UNDEFINED ) { 198 bbox = new StringBuffer( this.ds.buildSRSTransformCall( bbox.toString(), targetSRSCode ) ); 199 } 200 201 // use the bbox operator (&&) to filter using the spatial index 202 query.append( "(" ); 203 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 204 query.append( " && " ); 205 query.append( bbox.toString() ); 206 query.addArgument( box, Types.OTHER ); 207 208 query.append( " AND intersects (" ); 209 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 210 query.append( ',' ); 211 appendGeometryArgument( query, getGeometryProperty( operation.getPropertyName() ), operation.getGeometry() ); 212 query.append( "))" ); 213 } 214 215 private void appendBBOXOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 216 throws DatastoreException, GeometryException { 217 218 Envelope env = operation.getGeometry().getEnvelope(); 219 MappedGeometryPropertyType geoProperty = this.getGeometryProperty( operation.getPropertyName() ); 220 221 String argumentSRS = null; 222 if ( env.getCoordinateSystem() != null ) { 223 argumentSRS = env.getCoordinateSystem().getName(); 224 } 225 String propertySRS = geoProperty.getCS().getName(); 226 int internalSRS = geoProperty.getMappingField().getSRS(); 227 228 int createSRSCode = getArgumentSRSCode( argumentSRS, propertySRS, internalSRS ); 229 PGboxbase box = PGgeometryAdapter.export( env ); 230 StringBuffer bbox = new StringBuffer( 323 ); 231 bbox.append( "SetSRID(?," + createSRSCode + ")" ); 232 233 int targetSRSCode = getTargetSRSCode( argumentSRS, propertySRS, internalSRS ); 234 if ( targetSRSCode != SRS_UNDEFINED ) { 235 bbox = new StringBuffer( this.ds.buildSRSTransformCall( bbox.toString(), targetSRSCode ) ); 236 } 237 238 // only the && operator uses the spatial index 239 // intersects, contains etc. do not use spatial indexing!!!! 240 query.append( "(" ); 241 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 242 query.append( " && " ); 243 query.append( bbox.toString() ); 244 query.addArgument( box, Types.OTHER ); 245 246 // it is necessary to add an explicit intersects as well, because the && operator only 247 // checks for intersection of the bbox with the bboxes of the geometries (and not the 248 // geometries themselves) 249 query.append( " AND intersects (" ); 250 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 251 query.append( ',' ); 252 query.append( bbox.toString() ); 253 query.addArgument( box, Types.OTHER ); 254 query.append( "))" ); 255 } 256 257 private void appendDWithinOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 258 throws GeometryException, DatastoreException { 259 query.append( "distance(" ); 260 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 261 query.append( ',' ); 262 appendGeometryArgument( query, this.getGeometryProperty( operation.getPropertyName() ), operation.getGeometry() ); 263 query.append( ")<=" ); 264 query.append( "" + operation.getDistance() ); 265 } 266 267 private void appendBeyondOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 268 throws GeometryException, DatastoreException { 269 query.append( "distance(" ); 270 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 271 query.append( ',' ); 272 appendGeometryArgument( query, this.getGeometryProperty( operation.getPropertyName() ), operation.getGeometry() ); 273 query.append( ")>" ); 274 query.append( "" + operation.getDistance() ); 275 } 276 277 /** 278 * Construct and append the geometry argument using the correct internal SRS and perform a 279 * transform call to the internal SRS of the {@link MappedGeometryPropertyType} if necessary. 280 * 281 * @param query 282 * @param geoProperty 283 * @param geometry 284 * @throws DatastoreException 285 * @throws GeometryException 286 */ 287 private void appendGeometryArgument( StatementBuffer query, MappedGeometryPropertyType geoProperty, 288 Geometry geometry ) 289 throws DatastoreException, GeometryException { 290 291 String argumentSRS = null; 292 if ( geometry.getCoordinateSystem() != null ) { 293 argumentSRS = geometry.getCoordinateSystem().getName(); 294 } 295 String propertySRS = geoProperty.getCS().getName(); 296 int internalSRS = geoProperty.getMappingField().getSRS(); 297 298 int createSRSCode = getArgumentSRSCode( argumentSRS, propertySRS, internalSRS ); 299 PGgeometry argument = PGgeometryAdapter.export( geometry, createSRSCode ); 300 301 int targetSRSCode = getTargetSRSCode( argumentSRS, propertySRS, internalSRS ); 302 if ( targetSRSCode != SRS_UNDEFINED ) { 303 query.append( ds.buildSRSTransformCall( "?", targetSRSCode ) ); 304 } else { 305 query.append( '?' ); 306 } 307 query.addArgument( argument, Types.OTHER ); 308 } 309 310 /** 311 * Returns the internal SRS code that must be used for the creation of a geometry argument used 312 * in a spatial operator. 313 * 314 * @param literalSRS 315 * @param propertySRS 316 * @param internalSrs 317 * @return the internal SRS code that must be used for the creation of a geometry argument 318 * @throws DatastoreException 319 */ 320 private int getArgumentSRSCode( String argumentSRS, String propertySRS, int internalSrs ) 321 throws DatastoreException { 322 int argumentSRSCode = internalSrs; 323 if ( argumentSRS == null ) { 324 argumentSRSCode = internalSrs; 325 } else if ( !propertySRS.equals( argumentSRS ) ) { 326 argumentSRSCode = this.ds.getNativeSRSCode( argumentSRS ); 327 if ( argumentSRSCode == SRS_UNDEFINED ) { 328 String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNKNOWN_SRS", 329 PostGISDatastore.class.getName(), argumentSRS ); 330 throw new DatastoreException( msg ); 331 } 332 } 333 return argumentSRSCode; 334 } 335 336 /** 337 * Returns the internal SRS code that must be used for the transform call for a geometry 338 * argument used in a spatial operator. 339 * 340 * @param literalSRS 341 * @param propertySRS 342 * @param internalSrs 343 * @return the internal SRS code that must be used for the transform call of a geometry 344 * argument, or -1 if no transformation is necessary 345 */ 346 private int getTargetSRSCode( String argumentSRS, String propertySRS, int internalSrs ) 347 throws DatastoreException { 348 int targetSRS = SRS_UNDEFINED; 349 if ( argumentSRS != null && !argumentSRS.equals( propertySRS ) ) { 350 if ( internalSrs == SRS_UNDEFINED ) { 351 String msg = Messages.getMessage( "DATASTORE_SRS_NOT_SPECIFIED2", argumentSRS, propertySRS ); 352 throw new DatastoreException( msg ); 353 } 354 targetSRS = internalSrs; 355 } 356 return targetSRS; 357 } 358 }