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 }