001: /*
002: * Geotools2 - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package org.geotools.arcsde.filter;
018:
019: import java.util.ArrayList;
020: import java.util.List;
021: import java.util.logging.Logger;
022:
023: import org.geotools.arcsde.data.ArcSDEGeometryBuilder;
024: import org.geotools.arcsde.data.ArcSDEGeometryBuildingException;
025: import org.geotools.data.DataSourceException;
026: import org.geotools.feature.FeatureType;
027: import org.geotools.filter.FilterCapabilities;
028: import org.opengis.filter.And;
029: import org.opengis.filter.ExcludeFilter;
030: import org.opengis.filter.Filter;
031: import org.opengis.filter.FilterVisitor;
032: import org.opengis.filter.Id;
033: import org.opengis.filter.IncludeFilter;
034: import org.opengis.filter.Not;
035: import org.opengis.filter.Or;
036: import org.opengis.filter.PropertyIsBetween;
037: import org.opengis.filter.PropertyIsEqualTo;
038: import org.opengis.filter.PropertyIsGreaterThan;
039: import org.opengis.filter.PropertyIsGreaterThanOrEqualTo;
040: import org.opengis.filter.PropertyIsLessThan;
041: import org.opengis.filter.PropertyIsLessThanOrEqualTo;
042: import org.opengis.filter.PropertyIsLike;
043: import org.opengis.filter.PropertyIsNotEqualTo;
044: import org.opengis.filter.PropertyIsNull;
045: import org.opengis.filter.expression.Literal;
046: import org.opengis.filter.expression.PropertyName;
047: import org.opengis.filter.spatial.BBOX;
048: import org.opengis.filter.spatial.Beyond;
049: import org.opengis.filter.spatial.BinarySpatialOperator;
050: import org.opengis.filter.spatial.Contains;
051: import org.opengis.filter.spatial.Crosses;
052: import org.opengis.filter.spatial.DWithin;
053: import org.opengis.filter.spatial.Disjoint;
054: import org.opengis.filter.spatial.Equals;
055: import org.opengis.filter.spatial.Intersects;
056: import org.opengis.filter.spatial.Overlaps;
057: import org.opengis.filter.spatial.Touches;
058: import org.opengis.filter.spatial.Within;
059:
060: import com.esri.sde.sdk.client.SeException;
061: import com.esri.sde.sdk.client.SeExtent;
062: import com.esri.sde.sdk.client.SeFilter;
063: import com.esri.sde.sdk.client.SeLayer;
064: import com.esri.sde.sdk.client.SeShape;
065: import com.esri.sde.sdk.client.SeShapeFilter;
066: import com.vividsolutions.jts.geom.Geometry;
067: import com.vividsolutions.jts.geom.GeometryCollection;
068: import com.vividsolutions.jts.geom.Polygon;
069:
070: /**
071: * Encodes the geometry related parts of a filter into a set of
072: * <code>SeFilter</code> objects and provides a method to get the resulting
073: * filters suitable to set up an SeQuery's spatial constraints.
074: *
075: * <p>
076: * Although not all filters support is coded yet, the strategy to filtering
077: * queries for ArcSDE datasources is separated in two parts, the SQL where
078: * clause construction, provided by <code>FilterToSQLSDE</code> and the spatial
079: * filters (or spatial constraints, in SDE vocabulary) provided here;
080: * mirroring the java SDE api approach
081: * </p>
082: *
083: * @author Gabriel Rold?n
084: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/arcsde/datastore/src/main/java/org/geotools/arcsde/filter/GeometryEncoderSDE.java $
085: */
086: public class GeometryEncoderSDE implements FilterVisitor {
087: /** Standard java logger */
088: private static Logger log = org.geotools.util.logging.Logging
089: .getLogger("org.geotools.filter");
090:
091: /** DOCUMENT ME! */
092: private static FilterCapabilities capabilities = new FilterCapabilities();
093:
094: static {
095: capabilities.addType(BBOX.class);
096: capabilities.addType(Contains.class);
097: capabilities.addType(Crosses.class);
098: capabilities.addType(Disjoint.class);
099: capabilities.addType(Equals.class);
100: capabilities.addType(Intersects.class);
101: capabilities.addType(Overlaps.class);
102: capabilities.addType(Within.class);
103: }
104:
105: /** DOCUMENT ME! */
106: private List sdeSpatialFilters = null;
107:
108: /** DOCUMENT ME! */
109: private SeLayer sdeLayer;
110:
111: private FeatureType featureType;
112:
113: /**
114: */
115: public GeometryEncoderSDE() {
116: //intentionally blank
117: }
118:
119: /**
120: */
121: public GeometryEncoderSDE(SeLayer layer, FeatureType featureType) {
122: this .sdeLayer = layer;
123: this .featureType = featureType;
124: }
125:
126: /**
127: * DOCUMENT ME!
128: *
129: * @param layer DOCUMENT ME!
130: *
131: * @deprecated remove when the old data api dissapear
132: */
133: public void setLayer(SeLayer layer) {
134: this .sdeLayer = layer;
135: }
136:
137: /**
138: * DOCUMENT ME!
139: *
140: * @return DOCUMENT ME!
141: */
142: public static FilterCapabilities getCapabilities() {
143: return capabilities;
144: }
145:
146: /**
147: * DOCUMENT ME!
148: *
149: * @return DOCUMENT ME!
150: */
151: public SeFilter[] getSpatialFilters() {
152: SeFilter[] filters = new SeFilter[this .sdeSpatialFilters.size()];
153:
154: return (SeFilter[]) this .sdeSpatialFilters.toArray(filters);
155: }
156:
157: /**
158: * DOCUMENT ME!
159: *
160: * @return DOCUMENT ME!
161: *
162: * @throws IllegalStateException DOCUMENT ME!
163: */
164: private String getLayerName() throws SeException {
165: if (this .sdeLayer == null) {
166: throw new IllegalStateException(
167: "SDE layer has not been set");
168: }
169: return this .sdeLayer.getQualifiedName();
170: }
171:
172: /**
173: * overriden just to avoid the "WHERE" keyword
174: *
175: * @param filter DOCUMENT ME!
176: *
177: * @throws GeometryEncoderException DOCUMENT ME!
178: */
179: public void encode(Filter filter) throws GeometryEncoderException {
180: this .sdeSpatialFilters = new ArrayList();
181: if (Filter.INCLUDE.equals(filter)) {
182: return;
183: }
184: if (capabilities.fullySupports(filter)) {
185: filter.accept(this , null);
186: } else {
187: throw new GeometryEncoderException("Filter type "
188: + filter.getClass() + " not supported");
189: }
190: }
191:
192: /**
193: * This is an internal handler so all the logic is in one place. The actual
194: * methods that call back to this method are the ones specified in the FilterVisitor
195: * interface
196: */
197: private Object visit(BinarySpatialOperator filter, Object extraData) {
198: try {
199: if (filter instanceof BBOX) {
200: addSpatialFilter((BinarySpatialOperator) filter,
201: SeFilter.METHOD_ENVP, true);
202: } else if (filter instanceof Contains) {
203: addSpatialFilter((BinarySpatialOperator) filter,
204: SeFilter.METHOD_PC, true);
205: } else if (filter instanceof Crosses) {
206: addSpatialFilter((BinarySpatialOperator) filter,
207: SeFilter.METHOD_LCROSS_OR_CP, true);
208: } else if (filter instanceof Disjoint) {
209: addSpatialFilter((BinarySpatialOperator) filter,
210: SeFilter.METHOD_II_OR_ET, false);
211: } else if (filter instanceof Equals) {
212: addSpatialFilter((BinarySpatialOperator) filter,
213: SeFilter.METHOD_IDENTICAL, true);
214: } else if (filter instanceof Intersects) {
215: addSpatialFilter((BinarySpatialOperator) filter,
216: SeFilter.METHOD_II_OR_ET, true);
217: } else if (filter instanceof Overlaps) {
218: addSpatialFilter((BinarySpatialOperator) filter,
219: SeFilter.METHOD_II, true);
220: addSpatialFilter((BinarySpatialOperator) filter,
221: SeFilter.METHOD_PC, false);
222: addSpatialFilter((BinarySpatialOperator) filter,
223: SeFilter.METHOD_SC, false);
224: } else if (filter instanceof Within) {
225: addSpatialFilter((BinarySpatialOperator) filter,
226: SeFilter.METHOD_SC, true);
227: } else {
228: // This shouldn't happen since we will have pulled out
229: // the unsupported parts before invoking this method
230: String msg = "unsupported filter type";
231: log.warning(msg);
232: }
233: } catch (Exception e) {
234: throw new RuntimeException("Error building SeFilter", e);
235: }
236: return extraData;
237: }
238:
239: /**
240: * DOCUMENT ME!
241: *
242: * @param filter DOCUMENT ME!
243: * @param sdeMethod DOCUMENT ME!
244: * @param truth DOCUMENT ME!
245: *
246: * @throws SeException DOCUMENT ME!
247: * @throws DataSourceException DOCUMENT ME!
248: * @throws GeometryBuildingException DOCUMENT ME!
249: */
250: private void addSpatialFilter(BinarySpatialOperator filter,
251: int sdeMethod, boolean truth) throws SeException,
252: DataSourceException, ArcSDEGeometryBuildingException {
253:
254: org.opengis.filter.expression.Expression left, right;
255: PropertyName propertyExpr;
256: Literal geomLiteralExpr;
257:
258: left = filter.getExpression1();
259: right = filter.getExpression2();
260: if (left instanceof PropertyName && right instanceof Literal) {
261: propertyExpr = (PropertyName) left;
262: geomLiteralExpr = (Literal) right;
263: } else if (right instanceof PropertyName
264: && left instanceof Literal) {
265: propertyExpr = (PropertyName) right;
266: geomLiteralExpr = (Literal) left;
267: } else {
268: String err = "SDE currently supports one geometry and one "
269: + "attribute expr. You gave: " + left + ", "
270: + right;
271: throw new DataSourceException(err);
272: }
273:
274: // Should probably assert that attExpr's property name is equal to
275: // spatialCol...
276:
277: //HACK: we want to support <namespace>:SHAPE, but current FM doesn't
278: //support it. I guess we should try stripping the prefix and seeing if that
279: //matches...
280: final String spatialCol = featureType.getDefaultGeometry()
281: .getLocalName();
282: final String rawPropName = propertyExpr.getPropertyName();
283: String localPropName = rawPropName;
284: if (rawPropName.indexOf(":") != -1) {
285: localPropName = rawPropName.substring(rawPropName
286: .indexOf(":") + 1);
287: }
288: if (!rawPropName.equalsIgnoreCase(spatialCol)
289: && !localPropName.equalsIgnoreCase(spatialCol)) {
290: throw new DataSourceException(
291: "When querying against a spatial "
292: + "column, your property name must match the spatial"
293: + " column name.You used '"
294: + propertyExpr.getPropertyName()
295: + "', but the DB's spatial column name is '"
296: + spatialCol + "'");
297: }
298: Geometry geom = (Geometry) geomLiteralExpr.getValue();
299:
300: // To prevent errors in ArcSDE, we first trim the user's Filter
301: // geometry to the extents of our layer.
302: ArcSDEGeometryBuilder gb = ArcSDEGeometryBuilder
303: .builderFor(Polygon.class);
304: SeExtent seExtent = this .sdeLayer.getExtent();
305:
306: //If a layer just has one point in it (or one very horizontal or vertical line) then we may have
307: // a layer extent that's a point or line. We need to correct this.
308: if (seExtent.getMaxX() == seExtent.getMinX()) {
309: seExtent = new SeExtent(seExtent.getMinX() - 100, seExtent
310: .getMinY(), seExtent.getMaxX() + 100, seExtent
311: .getMaxY());
312: }
313: if (seExtent.getMaxY() == seExtent.getMinY()) {
314: seExtent = new SeExtent(seExtent.getMinX(), seExtent
315: .getMinY() - 100, seExtent.getMaxX(), seExtent
316: .getMaxY() + 100);
317: }
318:
319: SeShape extent = new SeShape(this .sdeLayer.getCoordRef());
320: extent.generateRectangle(seExtent);
321:
322: Geometry layerEnv = gb.construct(extent);
323: geom = geom.intersection(layerEnv); // does the work
324:
325: // Now make an SeShape
326: SeShape filterShape;
327:
328: //this is a bit hacky, but I don't yet know this code well enough
329: //to do it right. Basically if the geometry collection is completely
330: //outside of the area of the layer then an intersection will return
331: //a geometryCollection (two seperate geometries not intersecting will
332: //be a collection of two). Passing this into GeometryBuilder causes
333: //an exception. So what I did was just look to see if it is a gc
334: //and if so then just make a null seshape, as it shouldn't match
335: //any features in arcsde. -ch
336: if (geom.getClass() == GeometryCollection.class) {
337: filterShape = new SeShape(this .sdeLayer.getCoordRef());
338: } else {
339: gb = ArcSDEGeometryBuilder.builderFor(geom.getClass());
340: filterShape = gb.constructShape(geom, this .sdeLayer
341: .getCoordRef());
342: }
343: // Add the filter to our list
344: SeShapeFilter shapeFilter = new SeShapeFilter(getLayerName(),
345: this .sdeLayer.getSpatialColumn(), filterShape,
346: sdeMethod, truth);
347: this .sdeSpatialFilters.add(shapeFilter);
348: }
349:
350: // The Spatial Operator methods (these call to the above visit() method
351: public Object visit(BBOX arg0, Object arg1) {
352: return visit((BinarySpatialOperator) arg0, arg1);
353: }
354:
355: public Object visit(Beyond arg0, Object arg1) {
356: return visit((BinarySpatialOperator) arg0, arg1);
357: }
358:
359: public Object visit(Contains arg0, Object arg1) {
360: return visit((BinarySpatialOperator) arg0, arg1);
361: }
362:
363: public Object visit(Crosses arg0, Object arg1) {
364: return visit((BinarySpatialOperator) arg0, arg1);
365: }
366:
367: public Object visit(Disjoint arg0, Object arg1) {
368: return visit((BinarySpatialOperator) arg0, arg1);
369: }
370:
371: public Object visit(DWithin arg0, Object arg1) {
372: return visit((BinarySpatialOperator) arg0, arg1);
373: }
374:
375: public Object visit(Equals arg0, Object arg1) {
376: return visit((BinarySpatialOperator) arg0, arg1);
377: }
378:
379: public Object visit(Intersects arg0, Object arg1) {
380: return visit((BinarySpatialOperator) arg0, arg1);
381: }
382:
383: public Object visit(Overlaps arg0, Object arg1) {
384: return visit((BinarySpatialOperator) arg0, arg1);
385: }
386:
387: public Object visit(Within arg0, Object arg1) {
388: return visit((BinarySpatialOperator) arg0, arg1);
389: }
390:
391: public Object visit(Touches arg0, Object arg1) {
392: return visit((BinarySpatialOperator) arg0, arg1);
393: }
394:
395: //These are the 'just to implement the interface' methods.
396: public Object visit(Id filter, Object extraData) {
397: return extraData;
398: }
399:
400: public Object visit(And arg0, Object arg1) {
401: return arg1;
402: }
403:
404: public Object visit(ExcludeFilter arg0, Object arg1) {
405: return arg1;
406: }
407:
408: public Object visit(IncludeFilter arg0, Object arg1) {
409: return arg1;
410: }
411:
412: public Object visit(Not arg0, Object arg1) {
413: return arg1;
414: }
415:
416: public Object visit(Or arg0, Object arg1) {
417: return arg1;
418: }
419:
420: public Object visit(PropertyIsBetween arg0, Object arg1) {
421: return arg1;
422: }
423:
424: public Object visit(PropertyIsEqualTo arg0, Object arg1) {
425: return arg1;
426: }
427:
428: public Object visit(PropertyIsGreaterThan arg0, Object arg1) {
429: return arg1;
430: }
431:
432: public Object visit(PropertyIsGreaterThanOrEqualTo arg0, Object arg1) {
433: return arg1;
434: }
435:
436: public Object visit(PropertyIsLessThan arg0, Object arg1) {
437: return arg1;
438: }
439:
440: public Object visit(PropertyIsLessThanOrEqualTo arg0, Object arg1) {
441: return arg1;
442: }
443:
444: public Object visit(PropertyIsLike arg0, Object arg1) {
445: return arg1;
446: }
447:
448: public Object visit(PropertyIsNotEqualTo arg0, Object arg1) {
449: return arg1;
450: }
451:
452: public Object visit(PropertyIsNull arg0, Object arg1) {
453: return arg1;
454: }
455:
456: public Object visitNullFilter(Object arg0) {
457: return arg0;
458: }
459: }
|