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    }