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 com.vividsolutions.jts.geom.Envelope;
008: import com.vividsolutions.jts.geom.Geometry;
009: import org.geotools.factory.FactoryRegistryException;
010: import org.geotools.factory.Hints;
011: import org.geotools.feature.AttributeType;
012: import org.geotools.feature.Feature;
013: import org.geotools.feature.FeatureCollection;
014: import org.geotools.feature.FeatureIterator;
015: import org.geotools.feature.FeatureType;
016: import org.geotools.feature.FeatureTypes;
017: import org.geotools.feature.IllegalAttributeException;
018: import org.geotools.feature.SchemaException;
019: import org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer;
020: import org.geotools.geometry.jts.ReferencedEnvelope;
021: import org.geotools.referencing.ReferencingFactoryFinder;
022: import org.opengis.filter.Filter;
023: import org.opengis.referencing.FactoryException;
024: import org.opengis.referencing.crs.CoordinateReferenceSystem;
025: import org.opengis.referencing.operation.MathTransform2D;
026: import org.opengis.referencing.operation.OperationNotFoundException;
027: import org.opengis.referencing.operation.TransformException;
028: import java.io.IOException;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.NoSuchElementException;
032:
033: /**
034: * Decorating feature collection which reprojects feature geometries to a particular coordinate
035: * reference system on the fly.
036: * <p>
037: * The coordinate reference system of feature geometries is looked up using
038: * {@link com.vividsolutions.jts.geom.Geometry#getUserData()}.
039: * </p>
040: * <p>
041: * The {@link #defaultSource} attribute can be set to specify a coordinate refernence system
042: * to transform from when one is not specified by teh geometry itself. Leaving the property
043: * null specifies that the geometry will not be transformed.
044: * </p>
045: * @author Justin Deoliveira, The Open Planning Project
046: *
047: */
048: public class ReprojectingFeatureCollection extends
049: DecoratingFeatureCollection {
050: /**
051: * The schema of reprojected features
052: */
053: FeatureType schema;
054:
055: /**
056: * The target coordinate reference system
057: */
058: CoordinateReferenceSystem target;
059:
060: /**
061: * Coordinate reference system to use when one is not
062: * specified on an encountered geometry.
063: */
064: CoordinateReferenceSystem defaultSource;
065:
066: /**
067: * MathTransform cache, keyed by source CRS
068: */
069: HashMap /*<CoordinateReferenceSystem,GeometryCoordinateSequenceTransformer>*/transformers;
070:
071: /**
072: * Transformation hints
073: */
074: Hints hints = new Hints(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);
075:
076: public ReprojectingFeatureCollection(FeatureCollection delegate,
077: CoordinateReferenceSystem target) throws SchemaException,
078: OperationNotFoundException, FactoryRegistryException,
079: FactoryException {
080: super (delegate);
081:
082: this .target = target;
083: this .schema = FeatureTypes.transform(delegate.getSchema(),
084: target);
085:
086: //create transform cache
087: transformers = new HashMap();
088:
089: //cache "default" transform
090: CoordinateReferenceSystem source = delegate.getSchema()
091: .getDefaultGeometry().getCoordinateSystem();
092:
093: if (source != null) {
094: MathTransform2D tx = (MathTransform2D) ReferencingFactoryFinder
095: .getCoordinateOperationFactory(hints)
096: .createOperation(source, target).getMathTransform();
097:
098: GeometryCoordinateSequenceTransformer transformer = new GeometryCoordinateSequenceTransformer();
099: transformer.setMathTransform(tx);
100: transformers.put(source, transformer);
101: } else {
102: //throw exception?
103: }
104: }
105:
106: public void setDefaultSource(CoordinateReferenceSystem defaultSource) {
107: this .defaultSource = defaultSource;
108: }
109:
110: public FeatureIterator features() {
111: return new ReprojectingFeatureIterator(delegate.features());
112: }
113:
114: public Iterator iterator() {
115: return new ReprojectingIterator(delegate.iterator());
116: }
117:
118: public void close(FeatureIterator iterator) {
119: if (iterator instanceof ReprojectingFeatureIterator) {
120: delegate.close(((ReprojectingFeatureIterator) iterator)
121: .getDelegate());
122: }
123:
124: iterator.close();
125: }
126:
127: public void close(Iterator iterator) {
128: if (iterator instanceof ReprojectingIterator) {
129: delegate.close(((ReprojectingIterator) iterator)
130: .getDelegate());
131: }
132: }
133:
134: public FeatureType getFeatureType() {
135: return schema;
136: }
137:
138: public FeatureType getSchema() {
139: return schema;
140: }
141:
142: public FeatureCollection subCollection(Filter filter) {
143: FeatureCollection sub = delegate.subCollection(filter);
144:
145: if (sub != null) {
146: try {
147: ReprojectingFeatureCollection wrapper = new ReprojectingFeatureCollection(
148: sub, target);
149: wrapper.setDefaultSource(defaultSource);
150:
151: return wrapper;
152: } catch (Exception e) {
153: throw new RuntimeException(e);
154: }
155: }
156:
157: return null;
158: }
159:
160: public Object[] toArray() {
161: Object[] array = delegate.toArray();
162:
163: for (int i = 0; i < array.length; i++) {
164: try {
165: array[i] = reproject((Feature) array[i]);
166: } catch (IOException e) {
167: throw new RuntimeException(e);
168: }
169: }
170:
171: return array;
172: }
173:
174: public Object[] toArray(Object[] a) {
175: Object[] array = delegate.toArray(a);
176:
177: for (int i = 0; i < array.length; i++) {
178: try {
179: array[i] = reproject((Feature) array[i]);
180: } catch (IOException e) {
181: throw new RuntimeException(e);
182: }
183: }
184:
185: return array;
186: }
187:
188: public ReferencedEnvelope getBounds() {
189: Envelope bounds = new Envelope();
190: Iterator i = iterator();
191:
192: try {
193: if (!i.hasNext()) {
194: bounds.setToNull();
195:
196: return ReferencedEnvelope.reference(bounds);
197: } else {
198: Feature first = (Feature) i.next();
199: bounds.init(first.getBounds());
200: }
201:
202: for (; i.hasNext();) {
203: Feature f = (Feature) i.next();
204: bounds.expandToInclude(f.getBounds());
205: }
206: } finally {
207: close(i);
208: }
209:
210: return ReferencedEnvelope.reference(bounds);
211: }
212:
213: public FeatureCollection collection() throws IOException {
214: return this ;
215: }
216:
217: Feature reproject(Feature feature) throws IOException {
218: Object[] attributes = new Object[schema.getAttributeCount()];
219:
220: for (int i = 0; i < attributes.length; i++) {
221: AttributeType type = schema.getAttributeType(i);
222:
223: Object object = feature.getAttribute(type.getName());
224:
225: if (object instanceof Geometry) {
226: //check for crs
227: Geometry geometry = (Geometry) object;
228: CoordinateReferenceSystem crs = (CoordinateReferenceSystem) geometry
229: .getUserData();
230:
231: if (crs == null) {
232: // no crs specified on geometry, check default
233: if (defaultSource != null) {
234: crs = defaultSource;
235: }
236: }
237:
238: if (crs != null) {
239: //if equal, nothing to do
240: if (!crs.equals(target)) {
241: GeometryCoordinateSequenceTransformer transformer = (GeometryCoordinateSequenceTransformer) transformers
242: .get(crs);
243:
244: if (transformer == null) {
245: transformer = new GeometryCoordinateSequenceTransformer();
246:
247: MathTransform2D tx;
248:
249: try {
250: tx = (MathTransform2D) ReferencingFactoryFinder
251: .getCoordinateOperationFactory(
252: hints).createOperation(
253: crs, target)
254: .getMathTransform();
255: } catch (Exception e) {
256: String msg = "Could not transform for crs: "
257: + crs;
258: throw (IOException) new IOException(msg)
259: .initCause(e);
260: }
261:
262: transformer.setMathTransform(tx);
263: transformers.put(crs, transformer);
264: }
265:
266: //do the transformation
267: try {
268: object = transformer.transform(geometry);
269: } catch (TransformException e) {
270: String msg = "Error occured transforming "
271: + geometry.toString();
272: throw (IOException) new IOException(msg)
273: .initCause(e);
274: }
275: }
276: }
277: }
278:
279: attributes[i] = object;
280: }
281:
282: try {
283: return schema.create(attributes, feature.getID());
284: } catch (IllegalAttributeException e) {
285: String msg = "Error creating reprojeced feature";
286: throw (IOException) new IOException(msg).initCause(e);
287: }
288: }
289:
290: class ReprojectingFeatureIterator implements FeatureIterator {
291: FeatureIterator delegate;
292:
293: public ReprojectingFeatureIterator(FeatureIterator delegate) {
294: this .delegate = delegate;
295: }
296:
297: public FeatureIterator getDelegate() {
298: return delegate;
299: }
300:
301: public boolean hasNext() {
302: return delegate.hasNext();
303: }
304:
305: public Feature next() throws NoSuchElementException {
306: Feature feature = delegate.next();
307:
308: try {
309: return reproject(feature);
310: } catch (IOException e) {
311: throw new RuntimeException(e);
312: }
313: }
314:
315: public void close() {
316: delegate = null;
317: }
318: }
319:
320: class ReprojectingIterator implements Iterator {
321: Iterator delegate;
322:
323: public ReprojectingIterator(Iterator delegate) {
324: this .delegate = delegate;
325: }
326:
327: public Iterator getDelegate() {
328: return delegate;
329: }
330:
331: public void remove() {
332: delegate.remove();
333: }
334:
335: public boolean hasNext() {
336: return delegate.hasNext();
337: }
338:
339: public Object next() {
340: Feature feature = (Feature) delegate.next();
341:
342: try {
343: return reproject(feature);
344: } catch (IOException e) {
345: throw new RuntimeException(e);
346: }
347: }
348: }
349: }
|