package nl.ipo.cds.etl.filtering;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import nl.ipo.cds.etl.Feature;
import nl.ipo.cds.etl.FeatureFilter;
import nl.ipo.cds.etl.FeatureOutputStream;
import nl.ipo.cds.etl.PersistableFeature;
import org.deegree.geometry.Geometry;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
/**
* FeatureOutputStream implementation that filters and clips features based
* on a given geometry and writes the resulting features to a wrapped output
* stream.
*
* Incomming features that are completely disjoint with the clip geometry are
* discarded (they are not written to the wrapped output stream). Other features
* have their geometry intersected with the clip geometry before they are
* written to the output stream.
*
* Currently feature classes should have a single single property of type
* org.deegree.geometry.Geometry.
*/
public class FeatureClipper implements FeatureFilter<PersistableFeature, PersistableFeature> {
private final Geometry clipGeometry;
private final Class<? extends PersistableFeature> featureClass;
private final PropertyDescriptor geometryDescriptor;
public FeatureClipper (final Geometry clipGeometry, final Class<? extends PersistableFeature> featureClass) {
if (clipGeometry == null) {
throw new NullPointerException ("clipGeometry cannot be null");
}
if (featureClass == null) {
throw new NullPointerException ("featureClass cannot be null");
}
this.clipGeometry = clipGeometry;
this.featureClass = featureClass;
// Locate the geometry property to use:
final BeanWrapper wrapper = new BeanWrapperImpl (featureClass);
final List<PropertyDescriptor> geometryDescriptors = new ArrayList<PropertyDescriptor> ();
for (final PropertyDescriptor pd: wrapper.getPropertyDescriptors()) {
if (Geometry.class.isAssignableFrom (pd.getPropertyType ()) && pd.getReadMethod () != null && pd.getWriteMethod () != null) {
geometryDescriptors.add (pd);
}
}
if (geometryDescriptors.size () > 1) {
// In the future we might want to support multiple geometry properties, currently exactly one must exist.
throw new IllegalArgumentException (String.format ("Feature class %s has multiple geometry properties", featureClass.getCanonicalName ()));
} else if (geometryDescriptors.size () == 1) {
geometryDescriptor = geometryDescriptors.get (0);
} else {
throw new IllegalArgumentException (String.format ("Feature class %s has no (writable) geometry property", featureClass.getCanonicalName ()));
}
}
public Geometry getClipGeometry () {
return clipGeometry;
}
public Class<? extends PersistableFeature> getFeatureClass () {
return featureClass;
}
@Override
public void processFeature (final PersistableFeature feature, final FeatureOutputStream<PersistableFeature> outputStream, final FeatureOutputStream<Feature> errorOutputStream) {
if (feature == null) {
throw new NullPointerException ("feature cannot be null");
}
final Geometry geometry = getGeometry (feature);
if (geometry == null) {
// The feature doesn't require clipping:
outputStream.writeFeature (feature);
return;
}
clipGeometry.setCoordinateSystem (geometry.getCoordinateSystem ());
if (clipGeometry.isDisjoint (geometry)) {
// The feature is completely outside the clip area:
return;
}
final Geometry clippedGeometry = geometry.getIntersection (clipGeometry);
if (clippedGeometry == null) {
return;
}
setGeometry (feature, clippedGeometry);
outputStream.writeFeature (feature);
}
private Geometry getGeometry (final PersistableFeature feature) {
try {
return (Geometry)geometryDescriptor.getReadMethod ().invoke (feature);
} catch (IllegalAccessException e) {
throw new RuntimeException (e);
} catch (IllegalArgumentException e) {
throw new RuntimeException (e);
} catch (InvocationTargetException e) {
throw new RuntimeException (e);
}
}
private void setGeometry (final PersistableFeature feature, final Geometry geometry) {
try {
geometryDescriptor.getWriteMethod ().invoke (feature, geometry);
} catch (IllegalAccessException e) {
throw new RuntimeException (e);
} catch (IllegalArgumentException e) {
throw new RuntimeException (e);
} catch (InvocationTargetException e) {
throw new RuntimeException (e);
}
}
@Override
public void finish () {
}
@Override
public boolean postProcess() {
return true;
}
}