/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.filter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.factory.CommonFactoryFinder; import org.geotools.util.Converters; import org.opengis.feature.simple.SimpleFeature; import org.opengis.filter.MultiValuedFilter.MatchAction; import com.vividsolutions.jts.geom.Geometry; /** * Implements a geometry filter. * * <p> * This filter implements a relationship - of some sort - between two geometry * expressions. Note that this comparison does not attempt to restict its * expressions to be meaningful. This means that it considers itself a valid * filter as long as it contains two <b>geometry</b> sub-expressions. It is * also slightly less restrictive than the OGC Filter specification because * it does not require that one sub-expression be an geometry attribute and * the other be a geometry literal. * </p> * * <p> * In other words, you may use this filter to compare two geometries in the * same feature, such as: attributeA inside attributeB? You may also compare * two literal geometries, although this is fairly meaningless, since it could * be reduced (ie. it is always either true or false). This approach is very * similar to that taken in the FilterCompare class. * </p> * * @author Rob Hranac, TOPP * * @source $URL$ * @version $Id$ * * @task REVISIT: make this class (and all filters) immutable, implement * cloneable and return new filters when calling addLeftGeometry and * addRightG Issues to think through: would be cleaner immutability to * have constructor called with left and right Geometries, but this does * not jive with SAX parsing, which is one of the biggest uses of * filters. But the alternative is not incredibly efficient either, as * there will be two filters that are just thrown away every time we * make a full geometry filter. These issues extend to most filters, as * just about all of them are mutable when creating them. Other issue * is that lots of code will need to be changed for immutability. * (comments by cholmes) - MUTABLE FACTORIES! Sax and immutability. */ public abstract class GeometryFilterImpl extends BinaryComparisonAbstract implements GeometryFilter { /** Class logger */ private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter"); protected MatchAction matchAction; protected GeometryFilterImpl(org.opengis.filter.FilterFactory factory, MatchAction matchAction) { super(factory); this.matchAction = matchAction; } protected GeometryFilterImpl(org.opengis.filter.FilterFactory factory,org.opengis.filter.expression.Expression e1,org.opengis.filter.expression.Expression e2, MatchAction matchAction) { super(factory,e1,e2); this.matchAction = matchAction; } protected GeometryFilterImpl(org.opengis.filter.FilterFactory factory) { this (factory, MatchAction.ANY); } protected GeometryFilterImpl(org.opengis.filter.FilterFactory factory,org.opengis.filter.expression.Expression e1,org.opengis.filter.expression.Expression e2) { this(factory,e1,e2, MatchAction.ANY); } /** * Constructor with filter type. * * @param filterType The type of comparison. * * @throws IllegalFilterException Non-geometry type. */ protected GeometryFilterImpl(short filterType) throws IllegalFilterException { super(CommonFactoryFinder.getFilterFactory(null)); if (isGeometryFilter(filterType)) { this.filterType = filterType; } else { throw new IllegalFilterException("Attempted to create geometry " + "filter with non-geometry type."); } } /** * Adds the 'left' value to this filter. * * @param leftGeometry Expression for 'left' value. * * @throws IllegalFilterException Filter is not internally consistent. * * @deprecated use {@link #setExpression1(org.opengis.filter.expression.Expression)} */ public final void addLeftGeometry(Expression leftGeometry) throws IllegalFilterException { setExpression1(leftGeometry); } public void setExpression1(org.opengis.filter.expression.Expression expression) { if (expression instanceof Expression) { Expression leftGeometry = (Expression) expression; // Checks if this is geometry filter or not and handles appropriately if (DefaultExpression.isGeometryExpression(leftGeometry.getType()) || permissiveConstruction) { super.setExpression1(leftGeometry); } else { throw new IllegalFilterException("Attempted to add (left)" + " non-geometry expression" + " to geometry filter."); } } else { // I guess we assume it is a good expression... super.setExpression1(expression); } } /** * Adds the 'right' value to this filter. * * @param rightGeometry Expression for 'right' value. * * @throws IllegalFilterException Filter is not internally consistent. * * @deprecated use {@link #set} * */ public final void addRightGeometry(Expression rightGeometry) throws IllegalFilterException { setExpression2(rightGeometry); } public void setExpression2(org.opengis.filter.expression.Expression expression) { if (expression instanceof Expression) { Expression rightGeometry = (Expression) expression; // Checks if this is math filter or not and handles appropriately if (DefaultExpression.isGeometryExpression(rightGeometry.getType()) || permissiveConstruction) { super.setExpression2(rightGeometry); } else { throw new IllegalFilterException("Attempted to add (right)" + " non-geometry" + "expression to geometry filter."); } } else { // I guess we assume it is a good expression... super.setExpression2(expression); } } /** * Retrieves the expression on the left side of the comparison. * * @return the expression on the left. * @deprecated use {@link org.opengis.filter.spatial.BinarySpatialOperator#getExpression1()} */ public final Expression getLeftGeometry() { return (Expression)getExpression1(); } /** * Retrieves the expression on the right side of the comparison. * * @return the expression on the right. * @deprecated use {@link org.opengis.filter.spatial.BinarySpatialOperator#getExpression2()} */ public final Expression getRightGeometry() { return (Expression)getExpression2(); } /** * Subclass convenience method for returning left expression as a * JTS geometry. * * * @deprecated use {@link org.geotools.filter#getGeometries(org.opengis.filter.expression.Expression expr, Object feature)} */ protected Geometry getLeftGeometry(Object feature) { org.opengis.filter.expression.Expression leftGeometry = getExpression1(); if (leftGeometry != null) { Object obj = leftGeometry.evaluate(feature,Geometry.class); //LOGGER.finer("leftGeom = " + o.toString()); return (Geometry) obj; } else if (feature instanceof SimpleFeature) { return (Geometry) ((SimpleFeature)feature).getDefaultGeometry(); } return null; } /** * Subclass convenience method for returning right expression as a * JTS geometry. * * @deprecated use {@link org.geotools.filter#getGeometries(org.opengis.filter.expression.Expression expr, Object feature)} */ protected Geometry getRightGeometry(Object feature) { org.opengis.filter.expression.Expression rightGeometry = getExpression2(); if (rightGeometry != null) { return (Geometry) rightGeometry.evaluate(feature,Geometry.class); } else if(feature instanceof SimpleFeature){ return (Geometry) ((SimpleFeature)feature).getDefaultGeometry(); } return null; } /** * NC - support for multiple values * Convenience method for returning expression as either a geometry or a list of geometries. */ protected static Object getGeometries(org.opengis.filter.expression.Expression expr, Object feature) { Object o = expr.evaluate(feature); if (o instanceof Collection) { List<Geometry> list = new ArrayList<Geometry>(); for (Object item : (Collection<Object>) o) { Geometry geometry = (Geometry) Converters.convert(item, Geometry.class); if (geometry != null) { list.add(geometry); } } return list.size()>0 ? list : null; } return Converters.convert(o, Geometry.class); } /** * Determines whether or not a given feature is 'inside' this filter. * * @param feature Specified feature to examine. * * @return Flag confirming whether or not this feature is inside filter. */ public boolean evaluate(SimpleFeature feature) { return evaluate((Object)feature); } /** * Return this filter as a string. * * @return String representation of this geometry filter. */ public String toString() { String operator = null; // Handles all normal geometry cases if (filterType == GEOMETRY_EQUALS) { operator = " equals "; } else if (filterType == GEOMETRY_DISJOINT) { operator = " disjoint "; } else if (filterType == GEOMETRY_INTERSECTS) { operator = " intersects "; } else if (filterType == GEOMETRY_CROSSES) { operator = " crosses "; } else if (filterType == GEOMETRY_WITHIN) { operator = " within "; } else if (filterType == GEOMETRY_CONTAINS) { operator = " contains "; } else if (filterType == GEOMETRY_OVERLAPS) { operator = " overlaps "; } else if (filterType == GEOMETRY_BEYOND) { operator = " beyond "; } else if (filterType == GEOMETRY_BBOX) { operator = " bbox "; } org.opengis.filter.expression.Expression leftGeometry = getExpression1(); org.opengis.filter.expression.Expression rightGeometry = getExpression2(); if ((expression1 == null) && (rightGeometry == null)) { return "[ " + "null" + operator + "null" + " ]"; } else if (leftGeometry == null) { return "[ " + "null" + operator + rightGeometry.toString() + " ]"; } else if (rightGeometry == null) { return "[ " + leftGeometry.toString() + operator + "null" + " ]"; } return "[ " + leftGeometry.toString() + operator + rightGeometry.toString() + " ]"; } /** * Compares this filter to the specified object. Returns true if the * passed in object is the same as this filter. Checks to make sure the * filter types are the same as well as the left and right geometries. * * @param obj - the object to compare this GeometryFilter against. * * @return true if specified object is equal to this filter; else false */ public boolean equals(Object obj) { if (obj instanceof GeometryFilterImpl) { GeometryFilterImpl geomFilter = (GeometryFilterImpl) obj; boolean isEqual = true; isEqual = geomFilter.getFilterType() == this.filterType; if( LOGGER.isLoggable(Level.FINEST) ) { LOGGER.finest("filter type match:" + isEqual + "; in:" + geomFilter.getFilterType() + "; out:" + this.filterType); } isEqual = (geomFilter.expression1 != null) ? (isEqual && geomFilter.expression1.equals(this.expression1)) : (isEqual && (this.expression1 == null)); if( LOGGER.isLoggable(Level.FINEST) ) { LOGGER.finest("left geom match:" + isEqual + "; in:" + geomFilter.expression1 + "; out:" + this.expression1); } isEqual = (geomFilter.expression2 != null) ? (isEqual && geomFilter.expression2.equals(this.expression2)) : (isEqual && (this.expression2 == null)); if( LOGGER.isLoggable(Level.FINEST) ) { LOGGER.finest("right geom match:" + isEqual + "; in:" + geomFilter.expression2 + "; out:" + this.expression2); } return isEqual; } else { return false; } } /** * Override of hashCode method. * * @return a hash code value for this geometry filter. */ public int hashCode() { org.opengis.filter.expression.Expression leftGeometry = getExpression1(); org.opengis.filter.expression.Expression rightGeometry = getExpression2(); int result = 17; result = (37 * result) + filterType; result = (37 * result) + ((leftGeometry == null) ? 0 : leftGeometry.hashCode()); result = (37 * result) + ((rightGeometry == null) ? 0 : rightGeometry.hashCode()); return result; } public MatchAction getMatchAction() { return matchAction; } public final boolean evaluate(Object feature) { Object object1 = getGeometries(getExpression1(), feature); Object object2 = getGeometries(getExpression2(), feature); if(object1 == null || object2 == null ){ // default behaviour: if the geometry that is to be filtered is not // there we default to not returning anything return false; } if (!(object1 instanceof Collection) && !(object2 instanceof Collection)) { return evaluateInternal((Geometry) object1, (Geometry) object2); } Collection<Geometry> leftValues = object1 instanceof Collection ? (Collection<Geometry>) object1 : Collections.<Geometry>singletonList((Geometry) object1); Collection<Geometry> rightValues = object2 instanceof Collection ? (Collection<Geometry>) object2 : Collections.<Geometry>singletonList((Geometry) object2); int count = 0; for (Geometry leftValue : leftValues) { for (Geometry rightValue : rightValues) { boolean temp = evaluateInternal(leftValue, rightValue); if (temp) { count++; } switch (matchAction){ case ONE: if (count > 1) return false; break; case ALL: if (!temp) return false; break; case ANY: if (temp) return true; break; } } } switch (matchAction){ case ONE: return (count == 1); case ALL: return true; case ANY: return false; default: return false; } } /** * Performs the calculation on the two geometries. * * @param left the geometry on the left of the equations (the geometry obtained from evaluating Expression1) * @param right the geometry on the right of the equations (the geometry obtained from evaluating Expression2) * @return true if the filter evaluates to true for the two geometries */ protected abstract boolean evaluateInternal(Geometry left, Geometry right); }