/*
* 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.filter.FilterCapabilities;
import org.geotools.filter.UnaliasSQLEncoder;
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
* x,y (longitude/latitude) column pairs..
*
* @author Rob Atkinson , SCO
* @author Debasish Sahu, debasish.sahu@rmsi.com
*
* @source $URL:
* http://svn.geotools.org/geotools/trunk/gt/modules/unsupported/geometryless/src/main/java/org/geotools/data/geometryless/filter/SQLEncoderLocationsXY.java $
*/
public class SQLEncoderLocationsXY extends UnaliasSQLEncoder {
/** 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 xcolumn = null;
private String ycolumn = 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 SQLEncoderLocationsXY(String xcolumn, String ycolumn) {
capabilities = createFilterCapabilities();
this.xcolumn = xcolumn;
this.ycolumn = ycolumn;
setSqlNameEscape("");
}
public SQLEncoderLocationsXY(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("( " + xcolumn + " < " + x2 + " and " + xcolumn + " > " + x1 + " and " + ycolumn
+ " < " + y2 + " and " + ycolumn + " > " + y1 + " )");
}
}