/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-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.caching.util; import java.util.Iterator; import java.util.Stack; import org.geotools.factory.CommonFactoryFinder; import org.opengis.filter.And; import org.opengis.filter.ExcludeFilter; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.FilterVisitor; import org.opengis.filter.Id; import org.opengis.filter.IncludeFilter; import org.opengis.filter.Not; import org.opengis.filter.Or; import org.opengis.filter.PropertyIsBetween; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsGreaterThan; import org.opengis.filter.PropertyIsGreaterThanOrEqualTo; import org.opengis.filter.PropertyIsLessThan; import org.opengis.filter.PropertyIsLessThanOrEqualTo; import org.opengis.filter.PropertyIsLike; import org.opengis.filter.PropertyIsNotEqualTo; import org.opengis.filter.PropertyIsNull; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.spatial.BBOX; import org.opengis.filter.spatial.Beyond; import org.opengis.filter.spatial.BinarySpatialOperator; import org.opengis.filter.spatial.Contains; import org.opengis.filter.spatial.Crosses; import org.opengis.filter.spatial.DWithin; import org.opengis.filter.spatial.Disjoint; import org.opengis.filter.spatial.Equals; import org.opengis.filter.spatial.Intersects; import org.opengis.filter.spatial.Overlaps; import org.opengis.filter.spatial.Touches; import org.opengis.filter.spatial.Within; import org.opengis.filter.temporal.After; import org.opengis.filter.temporal.AnyInteracts; import org.opengis.filter.temporal.Before; import org.opengis.filter.temporal.Begins; import org.opengis.filter.temporal.BegunBy; import org.opengis.filter.temporal.BinaryTemporalOperator; import org.opengis.filter.temporal.During; import org.opengis.filter.temporal.EndedBy; import org.opengis.filter.temporal.Ends; import org.opengis.filter.temporal.Meets; import org.opengis.filter.temporal.MetBy; import org.opengis.filter.temporal.OverlappedBy; import org.opengis.filter.temporal.TContains; import org.opengis.filter.temporal.TEquals; import org.opengis.filter.temporal.TOverlaps; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; /** The purpose of this class is to split any Filter into two filters : * <ol><ul> a SpatialRestriction * <ul> and an OtherAttributesRestriction * <ol> * so we have : * OriginalFilter = SpatialRestriction && OtherAttributeRestriction * * SpatialRestriction may actually be a rough approximation of OtherAttributeRestriction * * @author Christophe Rousson, SoC 2007, CRG-ULAVAL * * * * * @source $URL$ */ public class BBoxFilterSplitter implements FilterVisitor { private static FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory2(null); private static final Envelope UNIVERSE_ENVELOPE = new Envelope(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); private static final Envelope EMPTY_ENVELOPE = new Envelope(); //envelopes that can be used to limit the //bounds of the data collected; if empty then equivalent to excludes filter private Stack<Envelope> envelopes = new Stack<Envelope>(); private Stack<Filter> otherRestrictions = new Stack<Filter>(); private String geom = null; private String srs = null; //private Stack notEnvelopes = new Stack() ; public Object visit(ExcludeFilter f, Object arg1) { envelopes.push(new Envelope(EMPTY_ENVELOPE)); return null; } public Object visit(IncludeFilter f, Object arg1) { envelopes.push(new Envelope(UNIVERSE_ENVELOPE)); return null; } public Object visit(Id f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(Not f, Object arg1) { //visit child f.getFilter().accept(this, arg1); //deal with envelope if (envelopes.size() > 0){ //universe - envelope = universe envelopes.pop(); envelopes.push(new Envelope(UNIVERSE_ENVELOPE)); } otherRestrictions.push(f); return null; } public Object visit(And f, Object arg1) { int envSize = envelopes.size(); int othSize = otherRestrictions.size(); for (Iterator<Filter> it = f.getChildren().iterator(); it.hasNext();) { Filter child = (Filter) it.next(); child.accept(this, arg1); } if (envelopes.size() >= (envSize + 2)) { Envelope e = (Envelope) envelopes.pop(); for (int i = envelopes.size(); i > envSize; i--) { Envelope curr = (Envelope) envelopes.pop(); if (curr.equals(EMPTY_ENVELOPE) || e.equals(EMPTY_ENVELOPE)){ e = new Envelope(EMPTY_ENVELOPE); }else if (curr.equals(UNIVERSE_ENVELOPE)){ //do nothing leave e alone //universe & envelope = enevelope }else if (e.equals(UNIVERSE_ENVELOPE )){ e = curr; }else{ //must expand to include instead of intersects //because two bounding boxes may be disjoint //but a geometry may still intersect both of the //bounding boxes e.expandToInclude(curr); } } envelopes.push(e); } // in all case, we'll need original filter as computed SpatialRestriction is a rough approximation multiplePop(otherRestrictions, othSize); Envelope top = envelopes.peek(); if (!(top.equals(EMPTY_ENVELOPE))){ otherRestrictions.push(f); } return null; } public Object visit(Or f, Object arg1) { int envSize = envelopes.size(); int othSize = otherRestrictions.size(); for (Iterator<Filter> it = f.getChildren().iterator(); it.hasNext();) { Filter child = (Filter) it.next(); child.accept(this, arg1); } if (envelopes.size() > (envSize + 1)) { Envelope e = (Envelope) envelopes.pop(); for (int i = envelopes.size(); i > envSize; i--) { e.expandToInclude((Envelope) envelopes.pop()); } envelopes.push(e); } else if (envelopes.size() == (envSize + 1)) { // the trick is we cannot separate this filter in the form of SpatialRestriction && OtherRestriction // so we add this part to OtherRestriction envelopes.pop(); envelopes.push(new Envelope(UNIVERSE_ENVELOPE)); } // in all case, we'll need original filter as computed SpatialRestriction is a rough approximation int size = otherRestrictions.size(); multiplePop(otherRestrictions, othSize); if (size > othSize){ otherRestrictions.push(f); } return null; } public Object visit(PropertyIsBetween f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(PropertyIsEqualTo f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(PropertyIsNotEqualTo f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(PropertyIsGreaterThan f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(PropertyIsGreaterThanOrEqualTo f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(PropertyIsLessThan f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(PropertyIsLessThanOrEqualTo f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(PropertyIsLike f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(PropertyIsNull f, Object arg1) { otherRestrictions.push(f); return null; } public Object visit(BBOX f, Object arg1) { if (geom == null) { if (f.getExpression1() instanceof PropertyName){ geom = ((PropertyName)f.getExpression1()).getPropertyName(); } srs = f.getSRS(); }else{ String newgeom = f.getExpression1() instanceof PropertyName ? ((PropertyName)f.getExpression1()).getPropertyName() : null; String newsrs = f.getSRS(); if ((geom != newgeom) || srs != srs ) { throw new UnsupportedOperationException( "This splitter can not be used against a filter where different BBOX filters refer to different Geometry attributes."); } } Envelope e = new Envelope(f.getMinX(), f.getMaxX(), f.getMinY(), f.getMaxY()); envelopes.push(e); return null; } public Object visit(Beyond f, Object arg1) { // we don't know how to handle this geometric restriction as a BBox // so we treat this as an attribute filter otherRestrictions.push(f); return null; } public Object visit(Contains f, Object arg1) { // we don't know how to handle this geometric restriction as a BBox // so we treat this as an attribute filter otherRestrictions.push(f); return null; } public Object visit(Crosses f, Object arg1) { // we don't know how to handle this geometric restriction as a BBox // so we treat this as an attribute filter otherRestrictions.push(f); return null; } public Object visit(Disjoint f, Object arg1) { // we don't know how to handle this geometric restriction as a BBox // so we treat this as an attribute filter otherRestrictions.push(f); return null; } public Object visit(DWithin f, Object arg1) { // we don't know how to handle this geometric restriction as a BBox // so we treat this as an attribute filter otherRestrictions.push(f); return null; } public Object visit(Equals f, Object arg1) { // we don't know how to handle this geometric restriction as a BBox // so we treat this as an attribute filter otherRestrictions.push(f); return null; } protected void traverse(BinarySpatialOperator f) { if (f.getExpression1() instanceof Literal) { Literal l = (Literal) f.getExpression1(); Geometry g = (Geometry) l.getValue(); envelopes.push(g.getEnvelopeInternal()); } else if (f.getExpression2() instanceof Literal) { Literal l = (Literal) f.getExpression2(); Geometry g = (Geometry) l.getValue(); envelopes.push(g.getEnvelopeInternal()); } if (f.getExpression1() instanceof PropertyName){ geom = ((PropertyName)f.getExpression1()).getPropertyName(); }else if (f.getExpression2() instanceof PropertyName){ geom = ((PropertyName)f.getExpression2()).getPropertyName(); } otherRestrictions.push(f); } public Object visit(Intersects f, Object arg1) { traverse(f); return null; } public Object visit(Overlaps f, Object arg1) { traverse(f); return null; } public Object visit(Touches f, Object arg1) { traverse(f); return null; } public Object visit(Within f, Object arg1) { traverse(f); return null; } public Object visitNullFilter(Object arg0) { // TODO Auto-generated method stub return null; } //temporal public Object visit(After after, Object extraData) { return visit((BinaryTemporalOperator)after, extraData); } public Object visit(AnyInteracts anyInteracts, Object extraData) { return visit((BinaryTemporalOperator)anyInteracts, extraData); } public Object visit(Before before, Object extraData) { return visit((BinaryTemporalOperator)before, extraData); } public Object visit(Begins begins, Object extraData) { return visit((BinaryTemporalOperator)begins, extraData); } public Object visit(BegunBy begunBy, Object extraData) { return visit((BinaryTemporalOperator)begunBy, extraData); } public Object visit(During during, Object extraData) { return visit((BinaryTemporalOperator)during, extraData); } public Object visit(EndedBy endedBy, Object extraData) { return visit((BinaryTemporalOperator)endedBy, extraData); } public Object visit(Ends ends, Object extraData) { return visit((BinaryTemporalOperator)ends, extraData); } public Object visit(Meets meets, Object extraData) { return visit((BinaryTemporalOperator)meets, extraData); } public Object visit(MetBy metBy, Object extraData) { return visit((BinaryTemporalOperator)metBy, extraData); } public Object visit(OverlappedBy overlappedBy, Object extraData) { return visit((BinaryTemporalOperator)overlappedBy, extraData); } public Object visit(TContains contains, Object extraData) { return visit((BinaryTemporalOperator)contains, extraData); } public Object visit(TEquals equals, Object extraData) { return visit((BinaryTemporalOperator)equals, extraData); } public Object visit(TOverlaps contains, Object extraData) { return visit((BinaryTemporalOperator)contains, extraData); } protected Object visit(BinaryTemporalOperator filter, Object data) { otherRestrictions.add(filter); return null; } public Envelope getEnvelope() { assert (envelopes.size() < 2); if (envelopes.isEmpty()) { return null; } else { return (Envelope) envelopes.peek(); } } /** Return the bbox part of original filter : * filter == (1) AND (2), where * (1) = BBOXImpl * (2) = other filter * * @return filter part (1) */ public Filter getFilterPre() { Envelope e = getEnvelope(); if (e == null || e.isNull()) { return Filter.EXCLUDE; //return Filter.INCLUDE; } else if (e.equals(UNIVERSE_ENVELOPE)){ return Filter.INCLUDE; } else { Filter myfilter = filterFactory.bbox(geom, e.getMinX(), e.getMinY(), e.getMaxX(), e.getMaxY(), srs); return myfilter; } } /** Return the non bbox part (2) of original filter : * filter == (1) AND (2), where * (1) = BBOXImpl * (2) = other filter * * @return filter part (2) */ public Filter getFilterPost() { if (otherRestrictions.isEmpty()) { return Filter.INCLUDE; } else if (otherRestrictions.size() == 1) { return (Filter) otherRestrictions.peek(); } else { return filterFactory.and(otherRestrictions.subList(0, otherRestrictions.size() - 1)); } } private void multiplePop(Stack<Filter> s, int downsize) { for (int i = s.size(); i > downsize; i--) { s.pop(); } } }