/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.geoserver.feature;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.geotools.factory.FactoryRegistryException;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.FeatureTypes;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.OperationNotFoundException;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.ProgressListener;
import com.vividsolutions.jts.geom.Geometry;
/**
* Decorating feature collection which reprojects feature geometries to a particular coordinate reference system on the fly.
* <p>
* The coordinate reference system of feature geometries is looked up using
* {@link com.vividsolutions.jts.geom.Geometry#getUserData()}.
* </p>
* <p>
* The {@link #defaultSource} attribute can be set to specify a coordinate
* refernence system to transform from when one is not specified by teh geometry
* itself. Leaving the property null specifies that the geometry will not be
* transformed.
* </p>
*
* @author Justin Deoliveira, The Open Planning Project
*
*/
public class ReprojectingFeatureCollection extends DecoratingFeatureCollection {
/**
* The schema of reprojected features
*/
SimpleFeatureType schema;
/**
* The target coordinate reference system
*/
CoordinateReferenceSystem target;
/**
* Coordinate reference system to use when one is not specified on an
* encountered geometry.
*/
CoordinateReferenceSystem defaultSource;
/**
* MathTransform cache, keyed by source CRS
*/
HashMap /* <CoordinateReferenceSystem,GeometryCoordinateSequenceTransformer> */transformers;
/**
* Transformation hints
*/
Hints hints = new Hints(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);
public ReprojectingFeatureCollection(
FeatureCollection<SimpleFeatureType, SimpleFeature> delegate,
CoordinateReferenceSystem target) throws SchemaException, OperationNotFoundException,
FactoryRegistryException, FactoryException {
super(delegate);
this.target = target;
this.schema = FeatureTypes.transform(delegate.getSchema(), target);
// create transform cache
transformers = new HashMap();
// cache "default" transform
CoordinateReferenceSystem source = delegate.getSchema().getCoordinateReferenceSystem();
if (source != null) {
MathTransform2D tx = (MathTransform2D) ReferencingFactoryFinder
.getCoordinateOperationFactory(hints).createOperation(source, target)
.getMathTransform();
GeometryCoordinateSequenceTransformer transformer = new GeometryCoordinateSequenceTransformer();
transformer.setMathTransform(tx);
transformers.put(source, transformer);
} else {
throw new RuntimeException("Source was null in trying to create a reprojected feature collection!");
}
}
@Override
public void accepts(FeatureVisitor visitor, ProgressListener progress) {
FeatureIterator<SimpleFeature> it = features();
try {
while (it.hasNext()) {
visitor.visit(it.next());
}
} finally {
close(it);
}
}
public void setDefaultSource(CoordinateReferenceSystem defaultSource) {
this.defaultSource = defaultSource;
}
public FeatureIterator<SimpleFeature> features() {
return new ReprojectingFeatureIterator(delegate.features());
}
public Iterator iterator() {
return new ReprojectingIterator(delegate.iterator());
}
public void close(FeatureIterator<SimpleFeature> iterator) {
if (iterator instanceof ReprojectingFeatureIterator) {
delegate.close(((ReprojectingFeatureIterator) iterator).getDelegate());
}
iterator.close();
}
public void close(Iterator iterator) {
if (iterator instanceof ReprojectingIterator) {
delegate.close(((ReprojectingIterator) iterator).getDelegate());
}
}
public SimpleFeatureType getFeatureType() {
return schema;
}
public SimpleFeatureType getSchema() {
return schema;
}
public FeatureCollection<SimpleFeatureType, SimpleFeature> subCollection(Filter filter) {
FeatureCollection<SimpleFeatureType, SimpleFeature> sub = delegate.subCollection(filter);
if (sub != null) {
try {
ReprojectingFeatureCollection wrapper = new ReprojectingFeatureCollection(sub,
target);
wrapper.setDefaultSource(defaultSource);
return wrapper;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return null;
}
public Object[] toArray() {
Object[] array = delegate.toArray();
for (int i = 0; i < array.length; i++) {
try {
array[i] = reproject((SimpleFeature) array[i]);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return array;
}
public Object[] toArray(Object[] a) {
Object[] array = delegate.toArray(a);
for (int i = 0; i < array.length; i++) {
try {
array[i] = reproject((SimpleFeature) array[i]);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return array;
}
public ReferencedEnvelope getBounds() {
ReferencedEnvelope bounds = null;
Iterator i = iterator();
try {
if (!i.hasNext()) {
bounds = new ReferencedEnvelope();
bounds.setToNull();
} else {
SimpleFeature first = (SimpleFeature) i.next();
bounds = new ReferencedEnvelope(first.getBounds());
}
for (; i.hasNext();) {
SimpleFeature f = (SimpleFeature) i.next();
bounds.include(f.getBounds());
}
return bounds;
} finally {
close(i);
}
}
public FeatureCollection<SimpleFeatureType, SimpleFeature> collection() throws IOException {
return this;
}
SimpleFeature reproject(SimpleFeature feature) throws IOException {
Object[] attributes = new Object[schema.getAttributeCount()];
for (int i = 0; i < attributes.length; i++) {
AttributeDescriptor type = schema.getDescriptor(i);
Object object = feature.getAttribute(type.getName());
if (object instanceof Geometry) {
// check for crs
Geometry geometry = (Geometry) object;
CoordinateReferenceSystem crs = (CoordinateReferenceSystem) geometry.getUserData();
if (crs == null) {
// no crs specified on geometry, check default
if (defaultSource != null) {
crs = defaultSource;
}
}
if (crs != null) {
// if equal, nothing to do
if (!crs.equals(target)) {
GeometryCoordinateSequenceTransformer transformer = (GeometryCoordinateSequenceTransformer) transformers
.get(crs);
if (transformer == null) {
transformer = new GeometryCoordinateSequenceTransformer();
MathTransform2D tx;
try {
tx = (MathTransform2D) ReferencingFactoryFinder
.getCoordinateOperationFactory(hints).createOperation(crs,
target).getMathTransform();
} catch (Exception e) {
String msg = "Could not transform for crs: " + crs;
throw (IOException) new IOException(msg).initCause(e);
}
transformer.setMathTransform(tx);
transformers.put(crs, transformer);
}
// do the transformation
try {
object = transformer.transform(geometry);
} catch (TransformException e) {
String msg = "Error occured transforming " + geometry.toString();
throw (IOException) new IOException(msg).initCause(e);
}
}
}
}
attributes[i] = object;
}
try {
return SimpleFeatureBuilder.build(schema, attributes, feature.getID());
} catch (IllegalAttributeException e) {
String msg = "Error creating reprojeced feature";
throw (IOException) new IOException(msg).initCause(e);
}
}
class ReprojectingFeatureIterator implements FeatureIterator<SimpleFeature> {
FeatureIterator<SimpleFeature> delegate;
public ReprojectingFeatureIterator(FeatureIterator<SimpleFeature> delegate) {
this.delegate = delegate;
}
public FeatureIterator<SimpleFeature> getDelegate() {
return delegate;
}
public boolean hasNext() {
return delegate.hasNext();
}
public SimpleFeature next() throws NoSuchElementException {
SimpleFeature feature = delegate.next();
try {
return reproject(feature);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void close() {
delegate = null;
}
}
class ReprojectingIterator implements Iterator<SimpleFeature> {
Iterator<SimpleFeature> delegate;
public ReprojectingIterator(Iterator<SimpleFeature> delegate) {
this.delegate = delegate;
}
public Iterator<SimpleFeature> getDelegate() {
return delegate;
}
public void remove() {
delegate.remove();
}
public boolean hasNext() {
return delegate.hasNext();
}
public SimpleFeature next() {
SimpleFeature feature = (SimpleFeature) delegate.next();
try {
return reproject(feature);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}