    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }