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 }