001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.geoserver.feature;
006:
007: import org.geotools.feature.AttributeType;
008: import org.geotools.feature.FeatureType;
009: import org.geotools.feature.GeometryAttributeType;
010: import org.geotools.filter.visitor.DuplicatingFilterVisitor;
011: import org.geotools.geometry.jts.JTS;
012: import org.geotools.geometry.jts.ReferencedEnvelope;
013: import org.geotools.referencing.CRS;
014: import org.opengis.filter.BinaryComparisonOperator;
015: import org.opengis.filter.FilterFactory2;
016: import org.opengis.filter.PropertyIsEqualTo;
017: import org.opengis.filter.PropertyIsNotEqualTo;
018: import org.opengis.filter.expression.Expression;
019: import org.opengis.filter.expression.Literal;
020: import org.opengis.filter.expression.PropertyName;
021: import org.opengis.filter.spatial.BBOX;
022: import org.opengis.filter.spatial.Beyond;
023: import org.opengis.filter.spatial.BinarySpatialOperator;
024: import org.opengis.filter.spatial.Contains;
025: import org.opengis.filter.spatial.Crosses;
026: import org.opengis.filter.spatial.DWithin;
027: import org.opengis.filter.spatial.Disjoint;
028: import org.opengis.filter.spatial.Equals;
029: import org.opengis.filter.spatial.Intersects;
030: import org.opengis.filter.spatial.Overlaps;
031: import org.opengis.filter.spatial.Touches;
032: import org.opengis.filter.spatial.Within;
033: import org.opengis.referencing.crs.CoordinateReferenceSystem;
034:
035: import com.vividsolutions.jts.geom.Geometry;
036:
037: /**
038: * Returns a clone of the provided filter where all geometries and bboxes have
039: * been reprojected to the CRS of the associated attributes. The working
040: * assumption is that the filters specified are strictly compliant with the OGC
041: * spec, so the first item is always a {@link PropertyName}, and the second
042: * always a {@link Literal}
043: *
044: * @author Andrea Aime - The Open Planning Project
045: *
046: */
047: public class ReprojectingFilterVisitor extends DuplicatingFilterVisitor {
048: FeatureType featureType;
049:
050: public ReprojectingFilterVisitor(FilterFactory2 factory,
051: FeatureType featureType) {
052: super (factory);
053: this .featureType = featureType;
054: }
055:
056: /**
057: * Returns the CRS associated to a property in the feature type. May be null
058: * if the property is not geometric, or if the CRS is not set
059: *
060: * @param propertyName
061: * @return
062: */
063: private CoordinateReferenceSystem findPropertyCRS(
064: PropertyName propertyName) {
065: AttributeType at = (AttributeType) propertyName
066: .evaluate(featureType);
067: if (at instanceof GeometryAttributeType) {
068: GeometryAttributeType gat = (GeometryAttributeType) at;
069: return gat.getCoordinateSystem();
070: } else {
071: return null;
072: }
073: }
074:
075: public Object visit(BBOX filter, Object extraData) {
076: // if no srs is specified we can't transform anyways
077: String srs = filter.getSRS();
078: if (srs == null || "".equals(srs.trim()))
079: return super .visit(filter, extraData);
080:
081: try {
082: // grab the original envelope data
083: double minx = filter.getMinX();
084: double miny = filter.getMinY();
085: double maxx = filter.getMaxX();
086: double maxy = filter.getMaxY();
087: CoordinateReferenceSystem crs = CRS.decode(srs);
088:
089: // grab the property data
090: String propertyName = filter.getPropertyName();
091: CoordinateReferenceSystem targetCrs = findPropertyCRS(factory
092: .property(propertyName));
093:
094: // if there is a mismatch, reproject and replace
095: if (crs != null && targetCrs != null
096: && !CRS.equalsIgnoreMetadata(crs, targetCrs)) {
097: ReferencedEnvelope envelope = new ReferencedEnvelope(
098: minx, maxx, miny, maxy, crs);
099: envelope = envelope.transform(targetCrs, true);
100: minx = envelope.getMinX();
101: miny = envelope.getMinY();
102: maxx = envelope.getMaxX();
103: maxy = envelope.getMaxY();
104: srs = targetCrs.getIdentifiers().iterator().next()
105: .toString();
106: }
107:
108: return getFactory(extraData).bbox(propertyName, minx, miny,
109: maxx, maxy, srs);
110: } catch (Exception e) {
111: throw new RuntimeException("Could not decode srs '" + srs
112: + "'", e);
113: }
114:
115: }
116:
117: public Object visit(PropertyIsEqualTo filter, Object extraData) {
118: return new BinaryComparisonTransformer() {
119:
120: Object cloneFilter(BinaryComparisonOperator filter,
121: Object extraData) {
122: return ReprojectingFilterVisitor.super .visit(
123: (PropertyIsEqualTo) filter, extraData);
124: }
125:
126: Object cloneFilter(BinaryComparisonOperator bso,
127: Object extraData, Expression ex1, Expression ex2) {
128: return factory.equal(ex1, ex2, bso.isMatchingCase());
129: }
130: }.transform(filter, extraData);
131: }
132:
133: public Object visit(PropertyIsNotEqualTo filter, Object extraData) {
134: return new BinaryComparisonTransformer() {
135:
136: Object cloneFilter(BinaryComparisonOperator filter,
137: Object extraData) {
138: return ReprojectingFilterVisitor.super .visit(
139: (PropertyIsNotEqualTo) filter, extraData);
140: }
141:
142: Object cloneFilter(BinaryComparisonOperator bso,
143: Object extraData, Expression ex1, Expression ex2) {
144: return factory.notEqual(ex1, ex2, bso.isMatchingCase());
145: }
146: }.transform(filter, extraData);
147: }
148:
149: public Object visit(Beyond filter, Object extraData) {
150: return new GeometryFilterTransformer() {
151:
152: Object cloneFilter(BinarySpatialOperator filter,
153: Object extraData) {
154: return ReprojectingFilterVisitor.super .visit(
155: (Beyond) filter, extraData);
156: }
157:
158: Object cloneFilter(BinarySpatialOperator bso,
159: Object extraData, Expression ex1, Expression ex2) {
160: Beyond filter = (Beyond) bso;
161: return factory.beyond(ex1, ex2, filter.getDistance(),
162: filter.getDistanceUnits());
163: }
164: }.transform(filter, extraData);
165: }
166:
167: public Object visit(Contains filter, Object extraData) {
168: return new GeometryFilterTransformer() {
169:
170: Object cloneFilter(BinarySpatialOperator filter,
171: Object extraData) {
172: return ReprojectingFilterVisitor.super .visit(
173: (Contains) filter, extraData);
174: }
175:
176: Object cloneFilter(BinarySpatialOperator bso,
177: Object extraData, Expression ex1, Expression ex2) {
178: return factory.contains(ex1, ex2);
179: }
180: }.transform(filter, extraData);
181: }
182:
183: public Object visit(Crosses filter, Object extraData) {
184: return new GeometryFilterTransformer() {
185:
186: Object cloneFilter(BinarySpatialOperator filter,
187: Object extraData) {
188: return ReprojectingFilterVisitor.super .visit(
189: (Crosses) filter, extraData);
190: }
191:
192: Object cloneFilter(BinarySpatialOperator bso,
193: Object extraData, Expression ex1, Expression ex2) {
194: return factory.crosses(ex1, ex2);
195: }
196: }.transform(filter, extraData);
197: }
198:
199: public Object visit(Disjoint filter, Object extraData) {
200: return new GeometryFilterTransformer() {
201:
202: Object cloneFilter(BinarySpatialOperator filter,
203: Object extraData) {
204: return ReprojectingFilterVisitor.super .visit(
205: (Disjoint) filter, extraData);
206: }
207:
208: Object cloneFilter(BinarySpatialOperator bso,
209: Object extraData, Expression ex1, Expression ex2) {
210: return factory.disjoint(ex1, ex2);
211: }
212: }.transform(filter, extraData);
213: }
214:
215: public Object visit(DWithin filter, Object extraData) {
216: return new GeometryFilterTransformer() {
217:
218: Object cloneFilter(BinarySpatialOperator filter,
219: Object extraData) {
220: return ReprojectingFilterVisitor.super .visit(
221: (DWithin) filter, extraData);
222: }
223:
224: Object cloneFilter(BinarySpatialOperator bso,
225: Object extraData, Expression ex1, Expression ex2) {
226: DWithin filter = (DWithin) bso;
227: return factory.dwithin(ex1, ex2, filter.getDistance(),
228: filter.getDistanceUnits());
229: }
230: }.transform(filter, extraData);
231: }
232:
233: public Object visit(Intersects filter, Object extraData) {
234: return new GeometryFilterTransformer() {
235:
236: Object cloneFilter(BinarySpatialOperator filter,
237: Object extraData) {
238: return ReprojectingFilterVisitor.super .visit(
239: (Intersects) filter, extraData);
240: }
241:
242: Object cloneFilter(BinarySpatialOperator bso,
243: Object extraData, Expression ex1, Expression ex2) {
244: return factory.intersects(ex1, ex2);
245: }
246: }.transform(filter, extraData);
247: }
248:
249: public Object visit(Overlaps filter, Object extraData) {
250: return new GeometryFilterTransformer() {
251:
252: Object cloneFilter(BinarySpatialOperator filter,
253: Object extraData) {
254: return ReprojectingFilterVisitor.super .visit(
255: (Overlaps) filter, extraData);
256: }
257:
258: Object cloneFilter(BinarySpatialOperator bso,
259: Object extraData, Expression ex1, Expression ex2) {
260: return factory.overlaps(ex1, ex2);
261: }
262: }.transform(filter, extraData);
263: }
264:
265: public Object visit(Touches filter, Object extraData) {
266: return new GeometryFilterTransformer() {
267:
268: Object cloneFilter(BinarySpatialOperator filter,
269: Object extraData) {
270: return ReprojectingFilterVisitor.super .visit(
271: (Touches) filter, extraData);
272: }
273:
274: Object cloneFilter(BinarySpatialOperator bso,
275: Object extraData, Expression ex1, Expression ex2) {
276: return factory.touches(ex1, ex2);
277: }
278: }.transform(filter, extraData);
279: }
280:
281: public Object visit(Within filter, Object extraData) {
282: return new GeometryFilterTransformer() {
283:
284: Object cloneFilter(BinarySpatialOperator filter,
285: Object extraData) {
286: return ReprojectingFilterVisitor.super .visit(
287: (Within) filter, extraData);
288: }
289:
290: Object cloneFilter(BinarySpatialOperator bso,
291: Object extraData, Expression ex1, Expression ex2) {
292: return factory.within(ex1, ex2);
293: }
294: }.transform(filter, extraData);
295: }
296:
297: public Object visit(Equals filter, Object extraData) {
298: return new GeometryFilterTransformer() {
299:
300: Object cloneFilter(BinarySpatialOperator filter,
301: Object extraData) {
302: return ReprojectingFilterVisitor.super .visit(
303: (Equals) filter, extraData);
304: }
305:
306: Object cloneFilter(BinarySpatialOperator bso,
307: Object extraData, Expression ex1, Expression ex2) {
308: return factory.equal(ex1, ex2);
309: }
310: }.transform(filter, extraData);
311: }
312:
313: /**
314: * Factors out most of the logic needed to reproject a geometry filter, leaving subclasses
315: * only the need to call the appropriate methods to create the new binary spatial filter
316: * @author Andrea Aime - The Open Plannig Project
317: *
318: */
319: private abstract class GeometryFilterTransformer {
320: Object transform(BinarySpatialOperator filter, Object extraData) {
321: // check working assumptions, first expression is a property
322: if (!(filter.getExpression1() instanceof PropertyName))
323: throw new IllegalArgumentException(
324: "Binary geometry filter, but first expression "
325: + "is not a property name? (it's a "
326: + filter.getExpression1().getClass()
327: + ")");
328: CoordinateReferenceSystem propertyCrs = findPropertyCRS((PropertyName) filter
329: .getExpression1());
330:
331: if (propertyCrs == null)
332: return cloneFilter(filter, extraData);
333:
334: // second expression is a geometry literal
335: if (!(filter.getExpression2() instanceof Literal))
336: throw new IllegalArgumentException(
337: "Binary geometry filter, but second expression "
338: + "is not a literal? (it's a "
339: + filter.getExpression1().getClass()
340: + ")");
341: Object value = ((Literal) filter.getExpression2())
342: .getValue();
343: if (!(value instanceof Geometry))
344: throw new IllegalArgumentException(
345: "Binary geometry filter, but second expression "
346: + "is not a geometry literal? (it's a "
347: + value.getClass() + ")");
348: Geometry geom = (Geometry) value;
349:
350: // does it make sense to proceed?
351: if (geom.getUserData() == null
352: || !(geom.getUserData() instanceof CoordinateReferenceSystem))
353: return cloneFilter(filter, extraData);
354:
355: try {
356: // reproject
357: CoordinateReferenceSystem geomCRS = (CoordinateReferenceSystem) geom
358: .getUserData();
359: Geometry transformed = JTS.transform(geom, CRS
360: .findMathTransform(geomCRS, propertyCrs, true));
361: transformed.setUserData(propertyCrs);
362:
363: // clone
364: Expression ex1 = (Expression) filter.getExpression1()
365: .accept(ReprojectingFilterVisitor.this ,
366: extraData);
367: Expression ex2 = factory.literal(transformed);
368: return cloneFilter(filter, extraData, ex1, ex2);
369: } catch (Exception e) {
370: throw new RuntimeException(
371: "Could not reproject geometry filter " + filter,
372: e);
373: }
374: }
375:
376: /**
377: * Straight cloning using cascaded visit
378: *
379: * @param filter
380: * @param extraData
381: * @return
382: */
383: abstract Object cloneFilter(BinarySpatialOperator filter,
384: Object extraData);
385:
386: /**
387: * Clone with the provided parameters as first and second expressions
388: *
389: * @param filter
390: * @param extraData
391: * @param ex1
392: * @param ex2
393: * @return
394: */
395: abstract Object cloneFilter(BinarySpatialOperator filter,
396: Object extraData, Expression ex1, Expression ex2);
397: }
398:
399: /**
400: * Factors out most of the logic needed to reproject a binary comparison filter, leaving subclasses
401: * only the need to call the appropriate methods to create the new binary spatial filter
402: * @author Andrea Aime - The Open Plannig Project
403: *
404: */
405: private abstract class BinaryComparisonTransformer {
406: Object transform(BinaryComparisonOperator filter,
407: Object extraData) {
408: // check working assumptions, first expression is a property
409: if (!(filter.getExpression1() instanceof PropertyName))
410: throw new IllegalArgumentException(
411: "Binary geometry filter, but first expression "
412: + "is not a property name? (it's a "
413: + filter.getExpression1().getClass()
414: + ")");
415: CoordinateReferenceSystem propertyCrs = findPropertyCRS((PropertyName) filter
416: .getExpression1());
417:
418: // we have to reproject only if the property is geometric and is compared against
419: // a geometric literal
420: if (!(propertyCrs != null
421: && (filter.getExpression2() instanceof Literal) && ((Literal) filter
422: .getExpression2()).getValue() instanceof Geometry))
423: return cloneFilter(filter, extraData);
424:
425: // extract the geometry
426: Object value = ((Literal) filter.getExpression2())
427: .getValue();
428: Geometry geom = (Geometry) value;
429:
430: // does it make sense to proceed?
431: if (geom.getUserData() == null
432: || !(geom.getUserData() instanceof CoordinateReferenceSystem))
433: return cloneFilter(filter, extraData);
434:
435: try {
436: // reproject
437: CoordinateReferenceSystem geomCRS = (CoordinateReferenceSystem) geom
438: .getUserData();
439: Geometry transformed = JTS.transform(geom, CRS
440: .findMathTransform(geomCRS, propertyCrs, true));
441: transformed.setUserData(propertyCrs);
442:
443: // clone
444: Expression ex1 = (Expression) filter.getExpression1()
445: .accept(ReprojectingFilterVisitor.this ,
446: extraData);
447: Expression ex2 = factory.literal(transformed);
448: return cloneFilter(filter, extraData, ex1, ex2);
449: } catch (Exception e) {
450: throw new RuntimeException(
451: "Could not reproject geometry filter " + filter,
452: e);
453: }
454: }
455:
456: /**
457: * Straight cloning using cascaded visit
458: *
459: * @param filter
460: * @param extraData
461: * @return
462: */
463: abstract Object cloneFilter(BinaryComparisonOperator filter,
464: Object extraData);
465:
466: /**
467: * Clone with the provided parameters as first and second expressions
468: *
469: * @param filter
470: * @param extraData
471: * @param ex1
472: * @param ex2
473: * @return
474: */
475: abstract Object cloneFilter(BinaryComparisonOperator filter,
476: Object extraData, Expression ex1, Expression ex2);
477:
478: }
479:
480: }
|