001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/model/filterencoding/SpatialOperation.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005       Department of Geography, University of Bonn
006     and
007       lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035    ----------------------------------------------------------------------------*/
036    package org.deegree.model.filterencoding;
037    
038    import static org.deegree.model.filterencoding.OperationDefines.BBOX;
039    
040    import org.deegree.framework.xml.ElementList;
041    import org.deegree.framework.xml.XMLTools;
042    import org.deegree.model.feature.Feature;
043    import org.deegree.model.spatialschema.GMLGeometryAdapter;
044    import org.deegree.model.spatialschema.Geometry;
045    import org.deegree.model.spatialschema.GeometryException;
046    import org.deegree.model.spatialschema.Surface;
047    import org.w3c.dom.Element;
048    
049    /**
050     * Encapsulates the information of a spatial_ops entity (as defined in the Filter DTD).
051     *
052     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
053     * @author <a href="mailto:luigimarinucci@yahoo.com">Luigi Marinucci<a>
054     * @author last edited by: $Author: mschneider $
055     *
056     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
057     */
058    public class SpatialOperation extends AbstractOperation {
059    
060        private Geometry geometry;
061    
062        private PropertyName propertyName;
063    
064        // calvin added on 10/21/2003
065        private double distance = -1;
066    
067        /**
068         * Constructs a new SpatialOperation.
069         *
070         * @param operatorId
071         *            The {@link OperationDefines} id
072         * @param propertyName
073         *            name of the property to do the spatial compare to.
074         * @param geometry
075         *            the geometry to do the spatial compare to.
076         *
077         * @see OperationDefines
078         */
079        public SpatialOperation( int operatorId, PropertyName propertyName, Geometry geometry ) {
080            super( operatorId );
081            this.propertyName = propertyName;
082            this.geometry = geometry;
083        }
084    
085        /**
086         * Constructs a new SpatialOperation.
087         *
088         * @param operatorId
089         *            The {@link OperationDefines} id
090         * @param propertyName
091         *            name of the property to do the spatial compare to.
092         * @param geometry
093         *            the geometry to do the spatial compare to.
094         * @param d
095         *            the distance (a buffer)
096         *
097         * @see OperationDefines Calvin added on 10/21/2003
098         *
099         */
100        public SpatialOperation( int operatorId, PropertyName propertyName, Geometry geometry, double d ) {
101            super( operatorId );
102            this.propertyName = propertyName;
103            this.geometry = geometry;
104            this.distance = d;
105        }
106    
107        /**
108         * returns the distance for geo spatial comparsions such as DWithin or Beyond
109         *
110         * @return the distance for geo spatial comparsions such as DWithin or Beyond
111         */
112        public double getDistance() {
113            return distance;
114        }
115    
116        /**
117         * Given a DOM-fragment, a corresponding Operation-object is built. This method recursively calls other buildFromDOM
118         * () - methods to validate the structure of the DOM-fragment.
119         *
120         * @throws FilterConstructionException
121         *             if the structure of the DOM-fragment is invalid
122         * @deprecated use the 1.0.0 filter encoding aware method instead.
123         */
124        @Deprecated
125        public static Operation buildFromDOM( Element element )
126                                throws FilterConstructionException {
127            return buildFromDOM( element, false );
128        }
129    
130        /**
131         * Given a DOM-fragment, a corresponding Operation-object is built. This method recursively calls other buildFromDOM
132         * () - methods to validate the structure of the DOM-fragment.
133         *
134         * @throws FilterConstructionException
135         *             if the structure of the DOM-fragment is invalid
136         */
137        public static Operation buildFromDOM( Element element, boolean useVersion_1_0_0 )
138                                throws FilterConstructionException {
139    
140            // check if root element's name is a spatial operator
141            String name = element.getLocalName();
142            int operatorId = OperationDefines.getIdByName( name );
143    
144            // every spatial operation must have exactly 2 elements (except BEYOND + DWITHIN)
145            ElementList children = XMLTools.getChildElements( element );
146    
147            if ( operatorId == OperationDefines.DWITHIN || operatorId == OperationDefines.BEYOND ) {
148                if ( children.getLength() != 3 ) {
149                    throw new FilterConstructionException( "'" + name + "' operator requires exactly 3 elements!" );
150                }
151            } else {
152                if ( children.getLength() != 2 && operatorId != BBOX ) {
153                    throw new FilterConstructionException( "'" + name + "' operator requires exactly 2 elements!" );
154                }
155            }
156    
157            // first element must be a PropertyName-element
158            Element child1 = children.item( 0 );
159            if ( !child1.getLocalName().toLowerCase().equals( "propertyname" ) && operatorId != BBOX ) {
160                throw new FilterConstructionException( "First element of every '" + name
161                                                       + "'-operation (except BBOX) must be a 'PropertyName'-element!" );
162            }
163    
164            PropertyName propertyName = null;
165            if ( child1.getLocalName().toLowerCase().equals( "propertyname" ) && !child1.getTextContent().equals( "" ) ) {
166                propertyName = (PropertyName) PropertyName.buildFromDOM( child1 );
167            } else {
168                if ( operatorId != BBOX ) {
169                    throw new FilterConstructionException( "First element of every '" + name
170                                                           + "'-operation (except BBOX) must be a 'PropertyName'-element!" );
171                }
172            }
173    
174            // second element must be a GML Geometry-element
175            Geometry geometry = null;
176            Element child2 = children.item( 1 );
177    
178            // this is really not a good way of parsing...
179            if ( !child1.getLocalName().toLowerCase().equals( "propertyname" ) ) {
180                child2 = child1;
181            }
182    
183            try {
184                geometry = GMLGeometryAdapter.wrap( child2, null );
185            } catch ( Exception e ) {
186                throw new FilterConstructionException( "GML Geometry definition in '" + name + "'-operation is erroneous: "
187                                                       + e.getMessage() );
188            }
189            if ( geometry == null ) {
190                throw new FilterConstructionException( "Unable to parse GMLGeometry definition in '" + name
191                                                       + "'-operation!" );
192            }
193    
194            double dist = -1;
195    
196            // BEYOND + DWITHIN have an additional Distance-element
197            if ( operatorId == OperationDefines.DWITHIN || operatorId == OperationDefines.BEYOND ) {
198                Element child3 = children.item( 2 );
199    
200                if ( !child3.getLocalName().toLowerCase().equals( "distance" ) ) {
201                    throw new FilterConstructionException( "Name of element does not equal 'Distance'!" );
202                }
203    
204                String distanceString = XMLTools.getStringValue( child3 );
205    
206                try {
207                    dist = Double.parseDouble( distanceString );
208                } catch ( NumberFormatException e ) {
209                    throw new FilterConstructionException( "Distance value is not a number: " + distanceString );
210                }
211                if ( dist < 0 ) {
212                    throw new FilterConstructionException( "Distance value can't be negative: " + distanceString );
213                }
214            }
215    
216            switch ( operatorId ) {
217            case OperationDefines.CROSSES:
218            case OperationDefines.BEYOND:
219            case OperationDefines.EQUALS:
220            case OperationDefines.OVERLAPS:
221            case OperationDefines.TOUCHES:
222            case OperationDefines.DISJOINT:
223            case OperationDefines.INTERSECTS:
224            case OperationDefines.WITHIN:
225            case OperationDefines.CONTAINS:
226            case OperationDefines.DWITHIN:
227                // every GMLGeometry is allowed as Literal-argument here
228                break;
229            case OperationDefines.BBOX: {
230                if ( !( geometry instanceof Surface ) ) {
231                    String msg = "'" + name + "'operator  can only be used with 'Envelope'-geometries!";
232                    throw new FilterConstructionException( msg );
233                }
234    
235                break;
236            }
237            default:
238                throw new FilterConstructionException( "'" + name + "' is not a known spatial operator!" );
239            }
240    
241            return new SpatialOperation( operatorId, propertyName, geometry, dist );
242        }
243    
244        /**
245         * Returns the geometry literal used in the operation.
246         *
247         * @return the literal as a <tt>GMLGeometry</tt>-object.
248         */
249        public Geometry getGeometry() {
250            return this.geometry;
251        }
252    
253        /**
254         * sets a geometry for a spatial operation
255         *
256         * @param geometry
257         */
258        public void setGeometry( Geometry geometry ) {
259            this.geometry = geometry;
260        }
261    
262        /**
263         * @return the name of the (spatial) property that shall be use for geo spatial comparsions
264         */
265        public PropertyName getPropertyName() {
266            return this.propertyName;
267        }
268    
269        public StringBuffer toXML() {
270            return to110XML();
271        }
272    
273        public StringBuffer to100XML() {
274            StringBuffer sb = new StringBuffer( 2000 );
275            sb.append( "<ogc:" ).append( getOperatorName() );
276            sb.append( " xmlns:gml='http://www.opengis.net/gml' " ).append( ">" );
277            if ( propertyName != null ) {
278                sb.append( propertyName.toXML() );
279            }
280            try {
281                if ( getOperatorName().equals( "BBOX" ) && geometry instanceof Surface ) {
282                    sb.append( GMLGeometryAdapter.exportAsBox( geometry.getEnvelope() ) );
283                } else {
284                    sb.append( GMLGeometryAdapter.export( geometry ) );
285                }
286            } catch ( GeometryException e ) {
287                e.printStackTrace();
288            }
289            if ( distance > 0 ) {
290                sb.append( "<ogc:Distance units=\"m\">" ).append( distance ).append( "</ogc:Distance>" );
291            }
292            sb.append( "</ogc:" ).append( getOperatorName() ).append( ">" );
293            return sb;
294        }
295    
296        public StringBuffer to110XML() {
297            StringBuffer sb = new StringBuffer( 2000 );
298            sb.append( "<ogc:" ).append( getOperatorName() );
299            sb.append( " xmlns:gml='http://www.opengis.net/gml' " ).append( ">" );
300            if ( propertyName != null ) {
301                sb.append( propertyName.toXML() );
302            }
303            try {
304                if ( getOperatorName().equals( "BBOX" ) && geometry instanceof Surface ) {
305                    sb.append( GMLGeometryAdapter.exportAsEnvelope( geometry.getEnvelope() ) );
306                } else {
307                    sb.append( GMLGeometryAdapter.export( geometry ) );
308                }
309            } catch ( GeometryException e ) {
310                e.printStackTrace();
311            }
312            if ( distance > 0 ) {
313                sb.append( "<ogc:Distance units=\"m\">" ).append( distance ).append( "</ogc:Distance>" );
314            }
315            sb.append( "</ogc:" ).append( getOperatorName() ).append( ">" );
316            return sb;
317        }
318    
319        /**
320         * Calculates the <tt>SpatialOperation</tt>'s logical value based on the property values of the given
321         * <tt>Feature</tt>.
322         * <p>
323         * TODO: Implement operations: CROSSES, BEYOND, OVERLAPS AND TOUCHES.
324         * <p>
325         *
326         * @param feature
327         *            that determines the property values
328         * @return true, if the <tt>SpatialOperation</tt> evaluates to true, else false
329         * @throws FilterEvaluationException
330         *             if the evaluation fails
331         */
332        public boolean evaluate( Feature feature )
333                                throws FilterEvaluationException {
334            boolean value = false;
335    
336            Geometry geom = getGeometry( feature );
337            if ( geom == null ) {
338                return false;
339            }
340    
341            switch ( operatorId ) {
342            case OperationDefines.EQUALS:
343                value = getGeometry( feature ).equals( getGeometry() );
344            case OperationDefines.DISJOINT: {
345                value = !getGeometry( feature ).intersects( getGeometry() );
346                break;
347            }
348            case OperationDefines.WITHIN: {
349                value = getGeometry().contains( getGeometry( feature ) );
350                break;
351            }
352            case OperationDefines.CONTAINS: {
353                value = getGeometry( feature ).contains( getGeometry() );
354                break;
355            }
356            case OperationDefines.INTERSECTS:
357            case OperationDefines.BBOX: {
358                value = getGeometry( feature ).intersects( getGeometry() );
359                break;
360            }
361                // calvin added on 10/21/2003
362            case OperationDefines.DWITHIN: {
363                value = getGeometry( feature ).isWithinDistance( getGeometry(), distance );
364                break;
365            }
366            case OperationDefines.CROSSES:
367            case OperationDefines.BEYOND:
368            case OperationDefines.OVERLAPS:
369            case OperationDefines.TOUCHES:
370                throw new FilterEvaluationException( "Evaluation for spatial operation '"
371                                                     + OperationDefines.getNameById( operatorId )
372                                                     + "' is not implemented yet!" );
373            default:
374                throw new FilterEvaluationException( "Encountered unexpected operatorId: " + operatorId
375                                                     + " in SpatialOperation.evaluate ()!" );
376            }
377    
378            return value;
379        }
380    
381        /**
382         * Returns the value of the targeted geometry property.
383         *
384         * @param feature
385         * @return the property value
386         * @throws FilterEvaluationException
387         *             if the PropertyName does not denote a Geometry
388         */
389        private Geometry getGeometry( Feature feature )
390                                throws FilterEvaluationException {
391    
392            Geometry geom = null;
393    
394            if ( this.propertyName != null ) {
395                Object propertyValue = this.propertyName.evaluate( feature );
396                if ( !( propertyValue instanceof Geometry ) ) {
397                    String msg = "Cannot evaluate spatial operation: property '" + this.propertyName
398                                 + "' does not denote a geometry property.";
399                    throw new FilterEvaluationException( msg );
400                }
401                geom = (Geometry) propertyValue;
402            } else {
403                Geometry[] geoms = feature.getGeometryPropertyValues();
404                if ( geoms == null || geoms.length < 1 ) {
405                    String msg = "Cannot evaluate spatial operation: feature has no geometry property.";
406                    throw new FilterEvaluationException( msg );
407                }
408                geom = geoms[0];
409            }
410            return geom;
411        }
412    
413        /**
414         * This is necessary for the BBOX operator, where the property name can/must be determined later.
415         *
416         * @param name
417         */
418        public void setPropertyName( PropertyName name ) {
419            propertyName = name;
420        }
421    
422    }