/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * (C) Copyright IBM Corporation, 2005-2007. All rights reserved. * * 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.db2; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.io.WKTWriter; import org.geotools.filter.DefaultExpression; import org.geotools.filter.FilterCapabilities; import org.geotools.jdbc.JDBCDataStore; import org.geotools.jdbc.PreparedFilterToSQL; import org.geotools.jdbc.PreparedStatementSQLDialect; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.filter.ExcludeFilter; import org.opengis.filter.Id; import org.opengis.filter.IncludeFilter; import org.opengis.filter.PropertyIsLike; import org.opengis.filter.expression.Literal; 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 java.io.IOException; import java.io.Writer; import java.util.HashMap; import java.util.Iterator; import java.util.logging.Logger; /** * Generate a WHERE clause for DB2 Spatial Extender based on a spatial filter. * * <p> * The following spatial filter operations are supported: * * <ul> * <li> * GEOMETRY_BBOX * </li> * <li> * GEOMETRY_CONTAINS * </li> * <li> * GEOMETRY_CROSSES * </li> * <li> * GEOMETRY_DISJOINT * </li> * <li> * GEOMETRY_EQUALS * </li> * <li> * GEOMETRY_INTERSECTS * </li> * <li> * GEOMETRY_OVERLAPS * </li> * <li> * GEOMETRY_TOUCHES * </li> * <li> * GEOMETRY_WITHIN * </li> * <li> * GEOMETRY_DWITHIN * </li> * </ul> * </p> * * @author Mueller Christian * * * @source $URL$ */ public class DB2FilterToSQL extends PreparedFilterToSQL{ private static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.data.db2"); // Class to convert geometry value into a Well-known Text string private static WKTWriter wktWriter = new WKTWriter(); //The standard SQL multicharacter wild card. private static char SQL_WILD_MULTI = '%'; //The standard SQL single character wild card. private static char SQL_WILD_SINGLE = '_'; // The escaped version of the single wildcard for the REGEXP pattern. private static String escapedWildcardSingle = "\\.\\?"; // The escaped version of the multiple wildcard for the REGEXP pattern. private static String escapedWildcardMulti = "\\.\\*"; static private HashMap<Class<?>,String> DB2_SPATIAL_PREDICATES = new HashMap<Class<?>,String>(); public DB2FilterToSQL(PreparedStatementSQLDialect dialect) { super(dialect); // TODO Auto-generated constructor stub } public DB2FilterToSQL(Writer out) { super(out); // TODO Auto-generated constructor stub } // Only intended for test purposes public HashMap getPredicateMap() { return DB2_SPATIAL_PREDICATES; } // The SELECTIVITY clause to be used with spatial predicates. private String selectivityClause = null; private boolean looseBBOXEnabled=false; { DB2_SPATIAL_PREDICATES.put(BBOX.class, "EnvelopesIntersect"); DB2_SPATIAL_PREDICATES.put(Contains.class, "ST_Contains"); DB2_SPATIAL_PREDICATES.put(Crosses.class, "ST_Crosses"); DB2_SPATIAL_PREDICATES.put(Disjoint.class, "ST_Disjoint"); DB2_SPATIAL_PREDICATES.put(Equals.class, "ST_Equals"); DB2_SPATIAL_PREDICATES.put(Intersects.class, "ST_Intersects"); DB2_SPATIAL_PREDICATES.put(Overlaps.class, "ST_Overlaps"); DB2_SPATIAL_PREDICATES.put(Touches.class, "ST_Touches"); DB2_SPATIAL_PREDICATES.put(Within.class, "ST_Within"); DB2_SPATIAL_PREDICATES.put(DWithin.class, "ST_Distance"); DB2_SPATIAL_PREDICATES.put(Beyond.class, "ST_Distance"); } /** HashMap<Class,String> example [BBOX.class,"EnvelopesIntersect"] */ static private HashMap getPredicateTable() { return DB2_SPATIAL_PREDICATES; } /** * Construct a geometry from the WKT representation of a geometry * * @param geom the constructor for the geometry. * */ public String db2Geom(Geometry geom) { String geomType = geom.getGeometryType(); String g1 = geom.toText(); String g2 = "db2gse.ST_" + geomType + "('" + g1 + "'," + getSRID() + ")"; return g2; } /** * Sets the DB2 filter capabilities. * * @return FilterCapabilities for DB2 */ protected FilterCapabilities createFilterCapabilities() { FilterCapabilities capabilities = new FilterCapabilities(); // New capbilities capabilities.addAll(FilterCapabilities.SIMPLE_COMPARISONS_OPENGIS); for( Iterator i=getPredicateTable().keySet().iterator(); i.hasNext(); ){ capabilities.addType( (Class) i.next() ); } capabilities.addType(PropertyIsLike.class); capabilities.addType(Id.class); // Old capabilities capabilities.addType(FilterCapabilities.LOGIC_OR); capabilities.addType(FilterCapabilities.LOGIC_AND); capabilities.addType(FilterCapabilities.LOGIC_NOT); capabilities.addType(FilterCapabilities.COMPARE_EQUALS); capabilities.addType(FilterCapabilities.COMPARE_NOT_EQUALS); capabilities.addType(FilterCapabilities.COMPARE_LESS_THAN); capabilities.addType(FilterCapabilities.COMPARE_GREATER_THAN); capabilities.addType(FilterCapabilities.COMPARE_LESS_THAN_EQUAL); capabilities.addType(FilterCapabilities.COMPARE_GREATER_THAN_EQUAL); capabilities.addType(FilterCapabilities.LIKE); capabilities.addType(FilterCapabilities.NULL_CHECK); capabilities.addType(FilterCapabilities.BETWEEN); capabilities.addType(FilterCapabilities.FID); capabilities.addType(FilterCapabilities.NONE); capabilities.addType(FilterCapabilities.ALL); capabilities.addType(FilterCapabilities.SPATIAL_BBOX); capabilities.addType(FilterCapabilities.SPATIAL_CONTAINS); capabilities.addType(FilterCapabilities.SPATIAL_CROSSES); capabilities.addType(FilterCapabilities.SPATIAL_DISJOINT); capabilities.addType(FilterCapabilities.SPATIAL_EQUALS); capabilities.addType(FilterCapabilities.SPATIAL_INTERSECT); capabilities.addType(FilterCapabilities.SPATIAL_OVERLAPS); capabilities.addType(FilterCapabilities.SPATIAL_TOUCHES); capabilities.addType(FilterCapabilities.SPATIAL_WITHIN); capabilities.addType(FilterCapabilities.SPATIAL_DWITHIN); capabilities.addType(FilterCapabilities.SPATIAL_BEYOND); // Does this need to be immutable??? return capabilities; } /** * Sets a SELECTIVITY clause that can be included with the spatial * predicate to influence the query optimizer to exploit a spatial index * if it exists. * * <p> * The parameter should be of the form: <br> * "SELECTIVITY 0.001" <br> * where the numeric value is the fraction of rows that will be returned * by using the index scan. This doesn't have to be true. The value * 0.001 is typically used to force the use of the spatial in all cases if * the spatial index exists. * </p> * * @param string a selectivity clause */ public void setSelectivityClause(String string) { this.selectivityClause = string; } /** * Encode BEYOND and DWITHIN filters using ST_Distance function. * * @param filter a BinarySpatialOperator (should be DWithin or Beyond subclass) * @param distance the distance value * @param distanceUnits the units for the distance operation or blank/null if not used * @param op the distance operator, either <. or >. * @param filter the GeometryDistanceFilter * * @throws RuntimeException */ private void encodeDistance(BinarySpatialOperator filter, double distance, String distanceUnits, String op) throws RuntimeException { DefaultExpression left = (DefaultExpression) filter.getExpression1(); DefaultExpression right = (DefaultExpression) filter.getExpression2(); try { int leftType = left.getType(); int rightType = right.getType(); // The test below should use ATTRIBUTE_GEOMETRY but only the value ATTRIBUTE if ((DefaultExpression.ATTRIBUTE == leftType) && (DefaultExpression.LITERAL_GEOMETRY == rightType)) { this.out.write("db2gse.ST_Distance("); left.accept(this,null); this.out.write(", "); right.accept(this,Geometry.class); if (!(distanceUnits == null || distanceUnits.length() == 0)) { // if units were specified this.out.write(", \"" + distanceUnits + "\""); } this.out.write(") " + op + " " + distance); addSelectivity(); // add selectivity clause if needed } else { String msg = "Unsupported left and right types: " + leftType + ":" + rightType; LOGGER.warning(msg); throw new RuntimeException(msg); } } catch (java.io.IOException e) { LOGGER.warning("Filter not generated; I/O problem of some sort" + e); } } public Object visit(DWithin filter, Object extraData) { double distance = filter.getDistance(); String distanceUnit = filter.getDistanceUnits(); encodeDistance(filter, distance, distanceUnit, "<"); return extraData; } public Object visit(Beyond filter, Object extraData) { double distance = filter.getDistance(); String distanceUnit = filter.getDistanceUnits(); encodeDistance(filter, distance, distanceUnit, ">"); return extraData; } protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Object extraData) { throw new RuntimeException( "SQLEncoderDB2 must implement this method in order to handle geometries"); // return extraData; } protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Object extraData, String db2Predicate) { LOGGER.finer("Generating GeometryFilter WHERE clause for " + filter); currentSRID=getSRID(); DefaultExpression left = (DefaultExpression) filter.getExpression1(); DefaultExpression right = (DefaultExpression) filter.getExpression2(); // neither left nor right expression can be null if ((null == left) || (null == right)) { String msg = "Left or right expression is null - " + filter; LOGGER.warning(msg); throw new RuntimeException(msg); } try { this.out.write("db2gse." + db2Predicate + "("); left.accept(this, extraData); this.out.write(", "); right.accept(this, extraData); this.out.write(") = 1"); addSelectivity(); // add selectivity clause if needed } catch (IOException e) { throw new RuntimeException(e); } LOGGER.fine(this.out.toString()); return extraData; } /** * Encode a bounding-box filter using the EnvelopesIntersect spatial * predicate. * * @param filter a BBOX filter object * @param extraData not used */ private Object encodeBBox(BBOX filter, Object extraData) { LOGGER.finer("Generating EnvelopesIntersect WHERE clause for " + filter); try { String spatialColumn = filter.getPropertyName(); if (spatialColumn == null || spatialColumn.trim().length()==0) { spatialColumn = featureType.getGeometryDescriptor().getLocalName(); } Integer srid = getSRID(spatialColumn); if (srid==null) { throw new RuntimeException("Attribute: "+spatialColumn+" is not registered"); } double minx = filter.getMinX(); double maxx = filter.getMaxX(); double miny = filter.getMinY(); double maxy = filter.getMaxY(); if (isLooseBBOXEnabled()) { this.out.write("db2gse.EnvelopesIntersect("); this.out.write(escapeName(spatialColumn)); this.out.write(", "); this.out.write(minx + ", " + miny + ", " + maxx + ", " + maxy + ", " + srid); this.out.write(") = 1"); } else { this.out.write("db2gse.st_intersects("); this.out.write(escapeName(spatialColumn)); this.out.write(", "); GeometryFactory fac = new GeometryFactory(); LinearRing linearRing = fac.createLinearRing(new Coordinate[] { new Coordinate(minx,miny), new Coordinate(maxx,miny), new Coordinate(maxx,maxy), new Coordinate(minx,maxy), new Coordinate(minx,miny), }); Polygon poly = fac.createPolygon(linearRing, null); this.out.write("db2gse.st_polyfromtext('"); WKTWriter writer = new WKTWriter(); this.out.write(writer.write(poly)); this.out.write("',"); this.out.write(srid.toString()); this.out.write(")) = 1"); } addSelectivity(); // add selectivity clause if needed } catch (java.io.IOException e) { LOGGER.warning("Filter not generated; I/O problem of some sort" + e); } return extraData; } public Object visit(BBOX filter, Object extraData) { return encodeBBox((BBOX)filter, extraData); } public Object visit(Contains filter, Object extraData) { return visitBinarySpatialOperator((BinarySpatialOperator)filter, extraData, "ST_Contains"); } public Object visit(Crosses filter, Object extraData) { return visitBinarySpatialOperator((BinarySpatialOperator)filter, extraData, "ST_Crosses"); } public Object visit(Disjoint filter, Object extraData) { return visitBinarySpatialOperator((BinarySpatialOperator)filter, extraData, "ST_Disjoint"); } public Object visit(Equals filter, Object extraData) { return visitBinarySpatialOperator((BinarySpatialOperator)filter, extraData, "ST_Equals"); } public Object visit(Intersects filter, Object extraData) { return visitBinarySpatialOperator((BinarySpatialOperator)filter, extraData, "ST_Intersects"); } public Object visit(Overlaps filter, Object extraData) { return visitBinarySpatialOperator((BinarySpatialOperator)filter, extraData, "ST_Overlaps"); } public Object visit(Touches filter, Object extraData) { return visitBinarySpatialOperator((BinarySpatialOperator)filter, extraData, "ST_Touches"); } public Object visit(Within filter, Object extraData) { return visitBinarySpatialOperator((BinarySpatialOperator)filter, extraData, "ST_Within"); } /** * Construct the appropriate geometry type from the WKT representation of a literal * expression * * @param expression the expression turn into a geometry constructor. * * @throws IOException Passes back exception if generated by * this.out.write() */ public void visitLiteralGeometry(Literal expression) throws IOException { String wktRepresentation = wktWriter.write((Geometry) expression.getValue()); int spacePos = wktRepresentation.indexOf(" "); String geomType = wktRepresentation.substring(0,spacePos); this.out.write("db2gse.ST_" + geomType + "('" + wktRepresentation + "', " + getSRID() + ")"); } protected void addSelectivity() throws IOException { if (this.selectivityClause != null) { this.out.write(" " + this.selectivityClause); } } /* (non-Javadoc) * @see org.geotools.data.jdbc.FilterToSQL#visit(org.opengis.filter.ExcludeFilter, java.lang.Object) */ public Object visit(ExcludeFilter filter, Object extraData) { try { out.write("1=0"); } catch (java.io.IOException ioe) {}; return extraData; } /* (non-Javadoc) * @see org.geotools.data.jdbc.FilterToSQL#visit(org.opengis.filter.IncludeFilter, java.lang.Object) */ public Object visit(IncludeFilter filter, Object extraData) { try { out.write("1=1"); } catch (java.io.IOException ioe) {}; return extraData; } private Integer getSRID() { return getSRID(featureType.getGeometryDescriptor()); } private Integer getSRID(String attrName) { AttributeDescriptor attrDescr = featureType.getDescriptor(attrName); if (attrDescr instanceof GeometryDescriptor) { return getSRID((GeometryDescriptor)attrDescr); } return currentSRID; } private Integer getSRID(GeometryDescriptor gDescr) { Integer result = null; if (gDescr != null) result = (Integer) gDescr.getUserData().get(JDBCDataStore.JDBC_NATIVE_SRID); if (result==null) result = currentSRID; return result; } public boolean isLooseBBOXEnabled() { return looseBBOXEnabled; } public void setLooseBBOXEnabled(boolean looseBBOXEnabled) { this.looseBBOXEnabled = looseBBOXEnabled; } }