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 }