package net.refractions.linecleaner;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Vector;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureStore;
import org.geotools.data.memory.MemoryFeatureCollection;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.shapefile.indexed.IndexedShapefileDataStore;
import org.geotools.data.shapefile.indexed.IndexedShapefileDataStoreFactory;
import org.geotools.factory.FactoryConfigurationError;
import org.geotools.feature.AttributeType;
import org.geotools.feature.AttributeTypeFactory;
import org.geotools.feature.Feature;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.FeatureType;
import org.geotools.feature.FeatureTypeBuilder;
import org.geotools.feature.FeatureTypeFactory;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.SchemaException;
import org.geotools.filter.AttributeExpression;
import org.geotools.filter.Expression;
import org.geotools.filter.FidFilter;
import org.geotools.filter.Filter;
import org.geotools.filter.FilterFactory;
import org.geotools.filter.FilterFactoryFinder;
import org.geotools.filter.FilterType;
import org.geotools.filter.GeometryDistanceFilter;
import org.geotools.filter.GeometryFilter;
import org.geotools.filter.IllegalFilterException;
import org.geotools.filter.function.FilterFunction_envelope;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
/**
* <p>
* Some useful functions on features and feature collections.
* </p>
* @author myronwu
* @author rgould
*/
public class FeatureUtil {
/** FeatureUtil MERGE_SOURCE_NAME field: the name of the attribute
* added to merged featurestores used to indicate the originating file. */
public static final String MERGE_SOURCE_NAME = "mrgsrc";
/**
* Returns a fresh copy of a feature.
* @param f
* @return Copy of f.
* @throws IllegalAttributeException
*/
public static Feature copy(Feature f)
throws IllegalAttributeException {
FeatureType type = f.getFeatureType();
// this may be deprecated, but so is FeatureFactory.create()
return type.create(f.getAttributes(null));
}
/**
* Get a collection of nearby features, where nearby means
* @param features FeatureCollection in which to look for nearby features.
* @param f Look for features near this feature f.
* @return FeatureCollection of features inside f's bounding box.
*/
public static FeatureCollection nearbyFeatures(FeatureCollection features, Feature f) {
FeatureCollection nearbyFeatures;
try {
Filter bboxFilter = FeatureUtil.nearish(f);
nearbyFeatures = features.subCollection( bboxFilter );
} catch (IllegalFilterException e) {
nearbyFeatures = new MemoryFeatureCollection(f.getFeatureType());
}
return nearbyFeatures;
}
public static FeatureCollection nearbyFeatureFids(FeatureSource source, Feature f) {
FeatureCollection nearbyFeatures = null;
String typename = source.getSchema().getTypeName();
String geomName = source.getSchema().getDefaultGeometry().getName();
try {
nearbyFeatures = source.getFeatures(
new DefaultQuery(typename, nearish(f), new String[] {geomName}));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return nearbyFeatures;
}
/**
* Get a filter to find features near f.
* @param f The feature that you want to find features near.
* @return The filter that'll return features near f.
* @throws IllegalFilterException
*/
public static Filter nearish( Feature f )
throws IllegalFilterException {
String geomName = f.getFeatureType().getDefaultGeometry().getName();
Envelope bounds = f.getBounds();
FilterFactory ff = FilterFactoryFinder.createFilterFactory();
GeometryFilter bboxFilter = ff.createGeometryFilter(FilterType.GEOMETRY_BBOX);
AttributeExpression geomExpr = ff.createAttributeExpression( geomName );
bboxFilter.addLeftGeometry(ff.createBBoxExpression(bounds));
FilterFunction_envelope ff_env = new FilterFunction_envelope();
ff_env.setArgs(new Expression[]{geomExpr});
bboxFilter.addRightGeometry(ff_env);
Filter fidFilter = ff.createFidFilter(f.getID());
return bboxFilter.and(fidFilter.not());
}
/**
* Get a filter that will fetch all features strictly within an envelope.
* @param geomName The name of the geometry attribute to be used in the filter.
* @param bounds The envelope inside which to find features.
* @return A filter that returns features inside bounds.
* @throws IllegalFilterException
*/
public static Filter withinBbox(String geomName, Envelope bounds)
throws IllegalFilterException {
FilterFactory ff = FilterFactoryFinder.createFilterFactory();
GeometryFilter bboxFilter = ff.createGeometryFilter(FilterType.GEOMETRY_BBOX);
AttributeExpression geomExpr = ff.createAttributeExpression( geomName );
bboxFilter.addLeftGeometry(ff.createBBoxExpression(bounds));
bboxFilter.addRightGeometry(geomExpr);
return bboxFilter;
}
public static Filter intersectsGeom(String geomName, Geometry g)
throws IllegalFilterException {
FilterFactory ff = FilterFactoryFinder.createFilterFactory();
GeometryFilter intersectFilter = ff.createGeometryFilter(FilterType.GEOMETRY_INTERSECTS);
AttributeExpression geomExpr = ff.createAttributeExpression(geomName);
intersectFilter.addLeftGeometry(ff.createLiteralExpression(g));
intersectFilter.addRightGeometry(geomExpr);
return intersectFilter;
}
public static Filter distanceToGeom(String geomName, Geometry g, double distance)
throws IllegalFilterException {
FilterFactory ff = FilterFactoryFinder.createFilterFactory();
GeometryDistanceFilter distanceFilter = ff.createGeometryDistanceFilter(FilterType.GEOMETRY_DWITHIN);
AttributeExpression geomExpr = ff.createAttributeExpression(geomName);
distanceFilter.addLeftGeometry(ff.createLiteralExpression(g));
distanceFilter.addRightGeometry(geomExpr);
distanceFilter.setDistance(distance);
return distanceFilter;
}
/**
* Retype all the features in a FeatureCollection.
* @param ft The type we want to expand fc's features to.
* @param fc The FeatureCollection to retype.
* @return The retyped features.
* @see org.geotools.data.DataUtilities#reType(org.geotools.feature.FeatureType, org.geotools.feature.Feature) reType
* @throws IllegalAttributeException
*/
@SuppressWarnings("unchecked")
public static FeatureCollection reType(FeatureType ft, FeatureCollection fc)
throws IllegalAttributeException {
FeatureCollection out = new MemoryFeatureCollection(ft);
FeatureIterator i = fc.features();
try {
while (i.hasNext()) {
Feature f = i.next();
out.add(DataUtilities.reType(ft, f));
}
} finally {
i.close();
}
return out;
}
/**
* Merge two feature types to produce a new FeatureType. Attributes are unioned.
* @param t First FeatureType to merge.
* @param s Second FeatureType to merge.
* @param name Name of the new FeatureType
* @return FeatureType that comprises t and s.
* @throws SchemaException
*/
public static FeatureType mergeFeatureTypes(FeatureType t, FeatureType s, String name)
throws SchemaException {
FeatureTypeBuilder builder = FeatureTypeFactory.newInstance(name);
builder.addTypes(mergeAttributeTypes(t,s));
FeatureType ft = builder.getFeatureType();
return ft;
}
// generalize an attribute type to have values of type string
private static AttributeType generalizeType(AttributeType t) {
return AttributeTypeFactory.newAttributeType(t.getName(), String.class, true);
}
/**
* Merge the two attribute type sets of two feature types. In general the attribute
* types are unioned, and in cases where two attribute types share a name but differ
* in class type, we generalize them to be String.
* @param t
* @param s
* @return
*/
public static AttributeType[] mergeAttributeTypes(FeatureType t, FeatureType s) {
Set<String> done = new HashSet<String>();
Vector<AttributeType> types = new Vector<AttributeType>();
// add in all attribute types of t
for (AttributeType type: t.getAttributeTypes()) {
AttributeType ttype = s.getAttributeType(type.getName());
if (ttype != null && !ttype.getType().equals(type.getType())) {
types.add(generalizeType(type));
done.add(type.getName());
} else {
types.add(type);
done.add(type.getName());
}
}
// add in all attribute types of s not found in t
for (AttributeType type: s.getAttributeTypes()) {
if (!done.contains(type.getName())) {
types.add(type);
}
}
return types.toArray(new AttributeType[types.size()]);
}
/**
* Add an attribute to a FeatureType.
* @param schema
* @param type
* @return New FeatureType with attribute type added to it.
* @throws SchemaException
*/
public static FeatureType addAttribute( FeatureType schema, AttributeType type )
throws SchemaException {
try {
FeatureType newFeatureType;
if (schema.find(type.getName()) == -1) {
FeatureTypeBuilder builder = FeatureTypeFactory.newInstance(schema.getTypeName());
builder.importType(schema);
builder.addType(type);
newFeatureType = builder.getFeatureType();
} else {
newFeatureType = schema;
}
return newFeatureType;
} catch (FactoryConfigurationError e) {
throw new SchemaException("Unable to add attribute to Feature.");
}
}
/**
* Adds a source file attribute to each feature in a FeatureCollection.
* @param fc
* @param source The value to set the attribute to.
* @return The features with the source file attribute added in.
* @throws SchemaException
* @throws IllegalAttributeException
*/
public static FeatureCollection addMergeSource(FeatureCollection fc, String source)
throws SchemaException, IllegalAttributeException {
FeatureType schema = fc.getSchema();
AttributeType mergeSourceAttribute =
AttributeTypeFactory.newAttributeType(MERGE_SOURCE_NAME,
String.class, true, 50, "");
FeatureType newType = addAttribute(schema, mergeSourceAttribute);
FeatureCollection result = new MemoryFeatureCollection(newType);
FeatureIterator i = fc.features();
try {
while (i.hasNext()) {
Feature f = i.next();
Feature newFeature = DataUtilities.reType(newType, f);
if (f.getAttribute(MERGE_SOURCE_NAME) == null) {
newFeature.setAttribute(MERGE_SOURCE_NAME, source);
result.add(newFeature);
} else {
result.add(f);
}
}
} finally {
i.close();
}
return result;
}
/**
*
* @param store
* @param fids
* @throws FactoryConfigurationError
* @throws IOException
*/
private static void removeFeatures( FeatureStore store, Collection<String> fids ) throws FactoryConfigurationError, IOException {
FilterFactory ff = FilterFactoryFinder.createFilterFactory();
FidFilter filter = ff.createFidFilter();
for (String fid: fids) {
filter.addFid(fid);
}
store.removeFeatures(filter);
}
public static void dumpFeature(Feature f) {
FeatureType ft = f.getFeatureType();
System.out.println(f.getDefaultGeometry());
/*
for (AttributeType at: ft.getAttributeTypes()) {
String name = at.getName();
System.out.print(name + ": " + f.getAttribute(name));
System.out.println("");
}*/
}
public static void dumpFeatureCollection( FeatureCollection fc ) {
FeatureIterator k = fc.features();
try {
while (k.hasNext()) {
Feature f = k.next();
dumpFeature(f);
}
} finally {
k.close();
}
}
public static void removeFeature(FeatureStore store, Feature f)
throws IOException {
FilterFactory ff = FilterFactoryFinder.createFilterFactory();
FidFilter filter = ff.createFidFilter();
filter.addFid(f.getID());
store.removeFeatures(filter);
}
public static void removeFeaturesByFid(FeatureStore store, Collection<String> fids)
throws IOException {
FilterFactory ff = FilterFactoryFinder.createFilterFactory();
FidFilter filter = ff.createFidFilter();
for (String fid: fids) {
filter.addFid(fid);
}
store.removeFeatures(filter);
}
public static void removeFeatures(FeatureStore store, FeatureCollection features)
throws IOException {
FilterFactory ff = FilterFactoryFinder.createFilterFactory();
FidFilter filter = ff.createFidFilter();
filter.addAllFids(getFids(features));
store.removeFeatures(filter);
}
public static FeatureStore copyFeatureStore(FeatureStore source, URL destination)
throws IOException {
IndexedShapefileDataStore ds = makeShapefileDataStore(destination, false);
ds.createSchema(source.getSchema());
FeatureStore store = (FeatureStore)ds.getFeatureSource();
store.addFeatures(source.getFeatures().reader());
return store;
}
public static void copyShapeFile(File source, File destination)
throws IOException {
String sourceNoExt = removeExtension(source.getAbsolutePath());
String destNoExt = removeExtension(destination.getAbsolutePath());
File dbfInFile = new File(sourceNoExt + ".dbf");
File shxInFile = new File(sourceNoExt + ".shx");
File dbfOutFile = new File(destNoExt + ".dbf");
File shxOutFile = new File(destNoExt + ".shx");
copyFile(source, destination);
copyFile(dbfInFile, dbfOutFile);
copyFile(shxInFile, shxOutFile);
}
protected static String removeExtension(String filename) {
int dotPlace = filename.lastIndexOf ( '.' );
return dotPlace >= 0 ? filename.substring(0, dotPlace) : filename;
}
protected static void copyFile(File source, File destination)
throws IOException {
InputStream in = new FileInputStream(source);
OutputStream out = new FileOutputStream(destination);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
}
/**
* Merge together two FeatureStores. Each feature will have a new attribute added to
* it indicating its source.
* @param one FeatureStore number one.
* @param labelOne The value for the source file attribute for features in FeatureStore one.
* @param two FeatureStore number two.
* @param labelTwo The value for the source file attribute for features in FeatureStore two.
* @param url The URL of where to store the new file.
* @return The new FeatureStore consisting of features from FeatureStores one and two.
* @throws IOException
* @throws MalformedURLException
*/
public static FeatureStore mergeFeatureStores(FeatureStore one, String labelOne, FeatureStore two, String labelTwo, URL url)
throws IOException {
try {
FeatureType type =
mergeFeatureTypes(one.getSchema(), two.getSchema(), one.getSchema().getTypeName());
type = addMergeSource(type);
ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
// Work on a temporary file. This is in case the destination is also
// a featurestore to be merged in; we can't overwrite before we read
// the features in.
File tmp = File.createTempFile("udigLineCleaner", ".shp");
IndexedShapefileDataStore ds = makeShapefileDataStore(tmp.toURL(), false);
ds.createSchema(type);
FeatureStore store = (FeatureStore)ds.getFeatureSource();
store.addFeatures(makeMergeFeatureReader(one, type, labelOne));
store.addFeatures(makeMergeFeatureReader(two, type, labelTwo));
FeatureStore copy = copyFeatureStore(store, url);
tmp.delete();
return copy;
// String file = url.getFile();
// copyShapeFile(tmp, new File(file));
// return openShapefileFeatureStore(file);
} catch (Exception e) {
// TODO Handle Exception
throw (RuntimeException) new RuntimeException( ).initCause( e );
}
}
public static FeatureType mergeFeatureTypes(List<FeatureStore> stores)
throws SchemaException {
FeatureType type = stores.get(0).getSchema();
int size = stores.size();
for (int i = 1; i < size; i++) {
type = mergeFeatureTypes(type, stores.get(i).getSchema(), type.getTypeName());
}
return type;
}
public static FeatureStore mergeFeatureStores(List<FeatureStore> storesToMerge, URL shpFileURL)
throws Exception {
try {
FeatureType type = addMergeSource(mergeFeatureTypes(storesToMerge));
ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
// Work on a temporary file. This is in case the destination is also
// a featurestore to be merged in; we can't overwrite before we read
// the features in.
File tmp = File.createTempFile("udigLineCleaner", ".shp");
IndexedShapefileDataStore ds = makeShapefileDataStore(tmp.toURL(), false);
ds.createSchema(type);
FeatureStore store = (FeatureStore)ds.getFeatureSource();
for (FeatureStore fs: storesToMerge) {
store.addFeatures(makeMergeFeatureReader(fs, type, fs.getSchema().getTypeName()));
}
// String file = shpFileURL.getFile();
// copyShapeFile(tmp, new File(file));
// return openShapefileFeatureStore(file);
FeatureStore copy = copyFeatureStore(store, shpFileURL);
tmp.delete();
return copy;
} catch (Exception e) {
// TODO Handle Exception
throw (RuntimeException) new RuntimeException( ).initCause( e );
}
}
public static FeatureStore openShapefileFeatureStore(String filename) throws Exception {
IndexedShapefileDataStore ds = openShapefileDataStore(filename);
return (FeatureStore) ds.getFeatureSource();
}
public static IndexedShapefileDataStore openShapefileDataStore(String filename) throws Exception {
File shp = new File(filename);
return makeShapefileDataStore(shp.toURL(), true);
}
public static IndexedShapefileDataStore makeShapefileDataStore(URL url, boolean spatial_index) throws IOException {
Map params = new HashMap();
params.put( IndexedShapefileDataStoreFactory.URLP.key, url);
params.put( IndexedShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key, new Boolean(spatial_index));
// params.put( IndexedShapefileDataStoreFactory.MEMORY_MAPPED.key, new Boolean(false));
// params.put( IndexedShapefileDataStoreFactory.SPATIAL_INDEX_TYPE.key, IndexedShapefileDataStoreFactory.TREE_GRX);
IndexedShapefileDataStoreFactory fac = new IndexedShapefileDataStoreFactory();
return (IndexedShapefileDataStore) fac.createDataStore(params);
}
/**
*
* @param type
* @return
* @throws SchemaException
*/
private static FeatureType addMergeSource( FeatureType type ) throws SchemaException {
if (type.getAttributeType(MERGE_SOURCE_NAME) == null) {
AttributeType mergeSourceAttribute =
AttributeTypeFactory.newAttributeType(MERGE_SOURCE_NAME,
String.class, true, 50, "");
type = addAttribute(type, mergeSourceAttribute);
}
return type;
}
private static FeatureReader makeMergeFeatureReader(FeatureStore source, final FeatureType type, final String label)
throws IOException {
final FeatureReader reader = source.getFeatures().reader();
return new FeatureReader() {
public FeatureType getFeatureType() {
return reader.getFeatureType();
}
public Feature next()
throws IOException, IllegalAttributeException,
NoSuchElementException {
Feature f = reader.next();
f = DataUtilities.reType(type, f);
if (f.getAttribute(MERGE_SOURCE_NAME) == null) {
f.setAttribute(MERGE_SOURCE_NAME, label);
}
return f;
}
public boolean hasNext() throws IOException {
return reader.hasNext();
}
public void close() throws IOException {
reader.close();
}
};
}
/**
*
* @param one
* @param label
* @param type
* @param store
* @throws IOException
* @throws IllegalAttributeException
*/
private static void copyFeatures( FeatureSource source, FeatureStore destination, String label )
throws IOException, IllegalAttributeException {
FeatureIterator i = source.getFeatures().features();
FeatureType type = destination.getSchema();
try {
while (i.hasNext()) {
Feature f = i.next();
f = DataUtilities.reType(type, f);
if (f.getAttribute(MERGE_SOURCE_NAME) == null) {
f.setAttribute(MERGE_SOURCE_NAME, label);
}
destination.addFeatures(makeReader(f));
}
} finally {
i.close();
}
}
private static void copyFeatures( FeatureIterator i, FeatureStore destination, String label )
throws IOException, IllegalAttributeException {
FeatureType type = destination.getSchema();
try {
while (i.hasNext()) {
Feature f = i.next();
f = DataUtilities.reType(type, f);
if (f.getAttribute(MERGE_SOURCE_NAME) == null) {
f.setAttribute(MERGE_SOURCE_NAME, label);
}
destination.addFeatures(makeReader(f));
}
} finally {
i.close();
}
}
public static FeatureReader makeReader(final Feature f) {
return new FeatureReader() {
public FeatureType getFeatureType() {
return f.getFeatureType();
}
boolean hasNext = true;
public Feature next()
throws IOException, IllegalAttributeException,
NoSuchElementException {
hasNext = false;
return f;
}
public boolean hasNext() throws IOException {
return hasNext;
}
public void close() throws IOException {
//do nothing.
}
};
}
/**
* Extracts a collection of geometries from a collection of features.
* @param features
* @return Geometries
*/
public static Collection<Geometry> extractGeometries(Collection<Feature> features) {
List<Geometry> result = new LinkedList();
for (Feature f: features) {
result.add(f.getDefaultGeometry());
}
return result;
}
/**
*
* @param store
* @return Does store have a merge source attribute?
*/
public static boolean hasMergeSourceAttribute(FeatureStore store) {
return store.getSchema().getAttributeType(FeatureUtil.MERGE_SOURCE_NAME) != null;
}
/**
* Filters a set of features to ensure they connect with one another. Any
* feature that doesn't connect to another through its two end points is dropped.
* @param f Feature guaranteed to be in the result set.
* @param features
* @return A collection of connected features.
*/
public static Collection<Feature> filterByConnectivity(Feature f, Collection<Feature> features) {
List<Feature> result = new LinkedList<Feature>();
Collection<Geometry> geometries = extractGeometries(features);
geometries.add(f.getDefaultGeometry());
for (Feature g: features) {
if (GeometryUtil.connects(g.getDefaultGeometry(), geometries)) {
result.add(g);
}
}
return result;
}
public static Collection<Feature> filterZeroLengthLines(Collection<Feature> features) {
List<Feature> result = new LinkedList<Feature>();
for (Feature f: features) {
if (f.getDefaultGeometry().getLength() != 0) {
result.add(f);
}
}
return result;
}
/**
*
* @param features
* @return fids of features in the feature collection.
*/
public static Collection<String> getFids(FeatureCollection features) {
Collection<String> fids = new HashSet<String>();
FeatureIterator i = features.features();
try {
while (i.hasNext()) {
fids.add(i.next().getID());
}
} finally {
i.close();
}
return fids;
}
public static Filter fidsToFidFilter(Collection<String> fids) {
FilterFactory ff = FilterFactoryFinder.createFilterFactory();
FidFilter filter = ff.createFidFilter();
filter.addAllFids(fids);
return filter;
}
}