/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geotools.data.wfs.v1_1_0; import java.util.List; import org.geotools.filter.visitor.DuplicatingFilterVisitor; import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.filter.BinaryComparisonOperator; import org.opengis.filter.FilterFactory2; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsNotEqualTo; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.ExpressionVisitor; import org.opengis.filter.expression.Function; 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.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.Geometry; /** * Returns a clone of the provided filter where all geometries and bboxes have * been reprojected to the CRS of the associated attributes. The working * assumption is that the filters specified are strictly compliant with the OGC * spec, so the first item is always a {@link PropertyName}, and the second * always a {@link Literal} * * @author Andrea Aime - The Open Planning Project * @todo: this class is copied from GeoServer, and it seems it would be good to port it to GeoTools * * @source $URL$ */ public class ReprojectingFilterVisitor extends DuplicatingFilterVisitor { SimpleFeatureType featureType; public ReprojectingFilterVisitor(FilterFactory2 factory, SimpleFeatureType featureType) { super(factory); this.featureType = featureType; } /** * Returns the CRS associated to a property in the feature type. May be null * if the property is not geometric, or if the CRS is not set * * @param propertyName * @return */ private CoordinateReferenceSystem findPropertyCRS(PropertyName propertyName) { AttributeDescriptor at = (AttributeDescriptor) propertyName.evaluate(featureType); if (at instanceof GeometryDescriptor) { GeometryDescriptor gat = (GeometryDescriptor) at; return gat.getCoordinateReferenceSystem(); } else { return null; } } public Object visit(BBOX filter, Object extraData) { // if no srs is specified we can't transform anyways String srs = filter.getSRS(); if (srs == null || "".equals(srs.trim())) return super.visit(filter, extraData); try { // grab the original envelope data double minx = filter.getMinX(); double miny = filter.getMinY(); double maxx = filter.getMaxX(); double maxy = filter.getMaxY(); CoordinateReferenceSystem crs = CRS.decode(srs); // grab the property data String propertyName = filter.getPropertyName(); CoordinateReferenceSystem targetCrs = findPropertyCRS(ff.property(propertyName)); // if there is a mismatch, reproject and replace if (crs != null && targetCrs != null && !CRS.equalsIgnoreMetadata(crs, targetCrs)) { ReferencedEnvelope envelope = new ReferencedEnvelope(minx, maxx, miny, maxy, crs); envelope = envelope.transform(targetCrs, true); minx = envelope.getMinX(); miny = envelope.getMinY(); maxx = envelope.getMaxX(); maxy = envelope.getMaxY(); srs = targetCrs.getIdentifiers().iterator().next().toString(); } return getFactory(extraData).bbox(propertyName, minx, miny, maxx, maxy, srs); } catch (Exception e) { throw new RuntimeException("Could not decode srs '" + srs + "'", e); } } public Object visit(PropertyIsEqualTo filter, Object extraData) { return new BinaryComparisonTransformer() { Object cloneFilter(BinaryComparisonOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((PropertyIsEqualTo) filter, extraData); } Object cloneFilter(BinaryComparisonOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.equal(ex1, ex2, bso.isMatchingCase()); } }.transform(filter, extraData); } public Object visit(PropertyIsNotEqualTo filter, Object extraData) { return new BinaryComparisonTransformer() { Object cloneFilter(BinaryComparisonOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((PropertyIsNotEqualTo) filter, extraData); } Object cloneFilter(BinaryComparisonOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.notEqual(ex1, ex2, bso.isMatchingCase()); } }.transform(filter, extraData); } public Object visit(Beyond filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((Beyond) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { Beyond filter = (Beyond) bso; return ff.beyond(ex1, ex2, filter.getDistance(), filter.getDistanceUnits()); } }.transform(filter, extraData); } public Object visit(Contains filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((Contains) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.contains(ex1, ex2); } }.transform(filter, extraData); } public Object visit(Crosses filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((Crosses) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.crosses(ex1, ex2); } }.transform(filter, extraData); } public Object visit(Disjoint filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((Disjoint) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.disjoint(ex1, ex2); } }.transform(filter, extraData); } public Object visit(DWithin filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((DWithin) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { DWithin filter = (DWithin) bso; return ff.dwithin(ex1, ex2, filter.getDistance(), filter.getDistanceUnits()); } }.transform(filter, extraData); } public Object visit(Intersects filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((Intersects) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.intersects(ex1, ex2); } }.transform(filter, extraData); } public Object visit(Overlaps filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((Overlaps) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.overlaps(ex1, ex2); } }.transform(filter, extraData); } public Object visit(Touches filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((Touches) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.touches(ex1, ex2); } }.transform(filter, extraData); } public Object visit(Within filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((Within) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.within(ex1, ex2); } }.transform(filter, extraData); } public Object visit(Equals filter, Object extraData) { return new GeometryFilterTransformer() { Object cloneFilter(BinarySpatialOperator filter, Object extraData) { return ReprojectingFilterVisitor.super.visit((Equals) filter, extraData); } Object cloneFilter(BinarySpatialOperator bso, Object extraData, Expression ex1, Expression ex2) { return ff.equal(ex1, ex2); } }.transform(filter, extraData); } /** * Helper method to reproject a geometry. */ protected Geometry reproject( Object value, CoordinateReferenceSystem propertyCrs) { if ( value == null ) { return null; } if (!(value instanceof Geometry)) throw new IllegalArgumentException("Binary geometry filter, but second expression " + "is not a geometry literal? (it's a " + value.getClass() + ")"); Geometry geom = (Geometry) value; // does it make sense to proceed? if (geom.getUserData() == null || !(geom.getUserData() instanceof CoordinateReferenceSystem)) return geom; try { // reproject CoordinateReferenceSystem geomCRS = (CoordinateReferenceSystem) geom.getUserData(); Geometry transformed = JTS.transform(geom, CRS.findMathTransform(geomCRS, propertyCrs, true)); transformed.setUserData(propertyCrs); return transformed; } catch(Exception e) { throw new RuntimeException("Could not reproject geometry " + value, e); } } Expression reproject(final Expression expression, final CoordinateReferenceSystem propertyCrs, boolean forceReprojection) { // check for case of section filter being a function if (expression instanceof Function) { //wrap the function in one that will transform the result final Function delegate = (Function) expression; return new FunctionReprojector(propertyCrs, delegate); } else if (expression instanceof Literal) { // second expression is a geometry literal Object value = ((Literal) expression).getValue(); return ff.literal(reproject(value,propertyCrs)); } else if(forceReprojection) { throw new IllegalArgumentException("Binary geometry filter, but second expression " + "is not a literal or function? (it's a " + expression.getClass() + ")"); } else { // we were not forced to reproject, then return the original expression return null; } } /** * Factors out most of the logic needed to reproject a geometry filter, leaving subclasses * only the need to call the appropriate methods to create the new binary spatial filter * @author Andrea Aime - The Open Plannig Project * */ private abstract class GeometryFilterTransformer { Object transform(final BinarySpatialOperator filter, Object extraData) { // check working assumptions, first expression is a property if (!(filter.getExpression1() instanceof PropertyName)) throw new IllegalArgumentException("Binary geometry filter, but first expression " + "is not a property name? (it's a " + filter.getExpression1().getClass() + ")"); final CoordinateReferenceSystem propertyCrs = findPropertyCRS((PropertyName) filter.getExpression1()); if (propertyCrs == null) return cloneFilter(filter, extraData); // "transformed" expressions Expression ex1 = (Expression) filter.getExpression1().accept( ReprojectingFilterVisitor.this, extraData); Expression ex2 = reproject(filter.getExpression2(), propertyCrs, true); return cloneFilter(filter, extraData, ex1, ex2 ); } /** * Straight cloning using cascaded visit * * @param filter * @param extraData * @return */ abstract Object cloneFilter(BinarySpatialOperator filter, Object extraData); /** * Clone with the provided parameters as first and second expressions * * @param filter * @param extraData * @param ex1 * @param ex2 * @return */ abstract Object cloneFilter(BinarySpatialOperator filter, Object extraData, Expression ex1, Expression ex2); } /** * Factors out most of the logic needed to reproject a binary comparison filter, leaving subclasses * only the need to call the appropriate methods to create the new binary spatial filter * @author Andrea Aime - The Open Plannig Project * */ private abstract class BinaryComparisonTransformer { Object transform(BinaryComparisonOperator filter, Object extraData) { // binary filters may use two random expressions, check if we have // enough information for a reprojection PropertyName name; Expression other; if ((filter.getExpression1() instanceof PropertyName)) { name = (PropertyName) filter.getExpression1(); other = filter.getExpression2(); } else if(filter.getExpression2() instanceof PropertyName) { name = (PropertyName) filter.getExpression2(); other = filter.getExpression1(); } else { return cloneFilter(filter, extraData); } CoordinateReferenceSystem propertyCrs = findPropertyCRS(name); // we have to reproject only if the property is geometric if (propertyCrs == null) return cloneFilter(filter, extraData); // "transformed" expressions Expression ex1 = (Expression) name.accept(ReprojectingFilterVisitor.this, extraData); Expression ex2 = reproject(other, propertyCrs, false); if(ex2 == null) ex2 = (Expression) other.accept(ReprojectingFilterVisitor.this, extraData); return cloneFilter(filter, extraData, ex1, ex2 ); } /** * Straight cloning using cascaded visit * * @param filter * @param extraData * @return */ abstract Object cloneFilter(BinaryComparisonOperator filter, Object extraData); /** * Clone with the provided parameters as first and second expressions * * @param filter * @param extraData * @param ex1 * @param ex2 * @return */ abstract Object cloneFilter(BinaryComparisonOperator filter, Object extraData, Expression ex1, Expression ex2); } /** * Makes sure that the result of a function gets reprojected to the specified CRS, should * it be a Geometry * @author Justin DeOliveira - TOPP * */ protected class FunctionReprojector implements Function { private final CoordinateReferenceSystem propertyCrs; private final Function delegate; protected FunctionReprojector(CoordinateReferenceSystem propertyCrs, Function delegate) { this.propertyCrs = propertyCrs; this.delegate = delegate; } public String getName() { return delegate.getName(); } public List getParameters() { return delegate.getParameters(); } public Object accept(ExpressionVisitor visitor, Object extraData) { return delegate.accept( visitor, extraData ); } public Object evaluate(Object object) { Object value = delegate.evaluate( object ); return reproject(value, propertyCrs); } public T evaluate(Object object, Class context) { T value = delegate.evaluate( object, context ); return (T) reproject(value, propertyCrs); } public Literal getFallbackValue() { return null; } } }