/*
* 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.data.geometryless.filter;
import java.io.IOException;
import java.util.logging.Logger;
import org.geotools.data.DataSourceException;
import org.geotools.data.jdbc.FilterToSQL;
import org.geotools.filter.FilterCapabilities;
import org.opengis.filter.ExcludeFilter;
import org.opengis.filter.Id;
import org.opengis.filter.IncludeFilter;
import org.opengis.filter.PropertyIsBetween;
import org.opengis.filter.PropertyIsLike;
import org.opengis.filter.PropertyIsNull;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.BinarySpatialOperator;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
/**
* Encodes a filter into a SQL WHERE statement for generic SQL. This class adds
* the ability to turn geometry filters into sql statements if they are
* based on xmin,ymin, xmax, ymax (longitude/latitude) columns ..
*
* @author Rob Atkinson , SCO
*
* @source $URL$
*/
public class SQLEncoderBBOX extends FilterToSQL {
/** Standard java logger */
private static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter");
/**
* The srid of the schema, so the bbox conforms. Could be better to have
* it in the bbox filter itself, but this works for now.
*/
private int srid;
// names of sql addressable columns containing numerical coordinates
private String XMinColumnName,YMinColumnName = null;
private String XMaxColumnName,YMaxColumnName = null;
private String geomName = null;
/** The geometry attribute to use if none is specified. */
private String defaultGeom;
/**
* Empty constructor TODO: rethink empty constructor, as BBOXes _need_ an
* SRID, must make client set it somehow. Maybe detect when encode is
* called?
*/
public SQLEncoderBBOX(String minx, String miny, String maxx, String maxy) {
capabilities = createFilterCapabilities();
this.XMinColumnName = minx;
this.YMinColumnName = miny;
this.XMaxColumnName = maxx;
this.YMaxColumnName = maxy;
this.geomName = geomName;
setSqlNameEscape("");
}
public SQLEncoderBBOX(int srid) {
this.srid = srid;
}
/**
* @see org.geotools.filter.SQLEncoder#createFilterCapabilities()
*/
protected FilterCapabilities createFilterCapabilities() {
FilterCapabilities capabilities = new FilterCapabilities();
capabilities.addAll(FilterCapabilities.LOGICAL_OPENGIS);
capabilities.addAll(FilterCapabilities.SIMPLE_COMPARISONS_OPENGIS);
capabilities.addType(PropertyIsNull.class);
capabilities.addType(PropertyIsBetween.class);
capabilities.addType(Id.class);
capabilities.addType(IncludeFilter.class);
capabilities.addType(ExcludeFilter.class);
capabilities.addType(PropertyIsLike.class);
capabilities.addType(BBOX.class);
return capabilities;
}
/**
* Sets a spatial reference system ESPG number, so that the geometry can be
* properly encoded for postgis. If geotools starts actually creating
* geometries with valid srids then this method will no longer be needed.
*
* @param srid the integer code for the EPSG spatial reference system.
*/
public void setSRID(int srid) {
this.srid = srid;
}
/**
* Sets the default geometry, so that filters with null for one of their
* expressions can assume that the default geometry is intended.
*
* @param name the name of the default geometry Attribute.
*
* @task REVISIT: pass in a featureType so that geometries can figure out
* their own default geometry?
*/
public void setDefaultGeometry(String name) {
//Do we really want clients to be using malformed filters?
//I mean, this is a useful method for unit tests, but shouldn't
//fully formed filters usually be used? Though I guess adding
//the option wouldn't hurt. -ch
this.defaultGeom = name;
}
/**
* Turns a geometry filter into the postgis sql bbox statement.
*
* @param filter the geometry filter to be encoded.
*
* @throws RuntimeException for IO exception (need a better error)
*/
public Object visitBinarySpatialOperator(BinarySpatialOperator filter, Object extraData) throws RuntimeException {
if (filter instanceof BBOX) {
Expression left = (Expression) filter.getExpression1();
Expression right = (Expression) filter.getExpression2();
PropertyName propertyExpr;
Literal geomLiteralExpr;
// left and right have to be valid expressions
try {
if (left instanceof PropertyName &&
right instanceof Literal) {
propertyExpr = (PropertyName)left;
geomLiteralExpr = (Literal)right;
} else if (right instanceof PropertyName &&
left instanceof Literal) {
propertyExpr = (PropertyName) right;
geomLiteralExpr = (Literal) left;
} else {
String err = "LocationsXY currently supports one geometry and one " +
"attribute expr. You gave: " + left + ", " + right;
throw new DataSourceException(err);
}
visitLiteralGeometry(geomLiteralExpr);
} catch (java.io.IOException ioe) {
LOGGER.warning("Unable to export filter" + ioe);
}
} else {
LOGGER.warning("exporting unknown filter type, only bbox supported");
throw new RuntimeException("Only BBox is currently supported");
}
return extraData;
}
/**
* Checks to see if the literal is a geometry, and encodes it if it is, if
* not just sends to the parent class.
*
* @param expression the expression to visit and encode.
*
* @throws IOException for IO exception (need a better error)
*/
public void visitLiteralGeometry(Literal expression)
throws IOException {
Geometry bbox = (Geometry) expression.getValue();
Envelope e = bbox.getEnvelopeInternal();
double x1 = e.getMinX();
double x2 = e.getMaxX();
double y1 = e.getMinY();
double y2 = e.getMaxY();
out.write( "( " + XMinColumnName + " < " + x2 + " and " + XMaxColumnName + " > " + x1 + " and " + YMinColumnName + " < " + y2 + " and " + YMaxColumnName + " > " + y1 + " )" );
}
}