package org.geotools.data.collection;
import java.awt.RenderingHints.Key;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.geotools.data.DataAccess;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureListener;
import org.geotools.data.FilteringFeatureReader;
import org.geotools.data.Query;
import org.geotools.data.QueryCapabilities;
import org.geotools.data.ReTypeFeatureReader;
import org.geotools.data.ResourceInfo;
import org.geotools.data.crs.ReprojectFeatureReader;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.store.EmptyFeatureCollection;
import org.geotools.data.store.ReTypingFeatureCollection;
import org.geotools.data.store.ReprojectingFeatureCollection;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.collection.AdaptorFeatureCollection;
import org.geotools.feature.collection.DecoratingSimpleFeatureCollection;
import org.geotools.feature.collection.FilteringSimpleFeatureCollection;
import org.geotools.feature.collection.MaxSimpleFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.sort.SortBy;
/**
* This is a "port" of ContentFeatureSource to work with an iterator.
* <p>
* To use this class please "wrap" CollectionFeatureSource around your choice of FeatureCollection.
*
* <pre>
* SimpleFeatureCollection collection = new ListFeatureCollection(schema);
* collection.add(feature1);
* collection.add(feature2);
* FeatureSource source = new CollectionFeatureSource(collection);
* </pre>
* <p>
* Note to implementors: If you are performing "real I/O" please use ContentFeatureSource as it
* provides support for IOException.
*
* @author Jody
*
* @source $URL: http://svn.osgeo.org/geotools/trunk/modules/library/main/src/main/java/org/geotools/data/collection/CollectionFeatureSource.java $
*/
public class CollectionFeatureSource implements SimpleFeatureSource {
protected SimpleFeatureCollection collection;
/**
* observers
*/
protected List<FeatureListener> listeners = null;
private QueryCapabilities capabilities;
private Set<Key> hints;
public CollectionFeatureSource(SimpleFeatureCollection collection) {
this.collection = collection;
}
public SimpleFeatureType getSchema() {
return collection.getSchema();
}
public synchronized void addFeatureListener(FeatureListener listener) {
if (listeners == null) {
listeners = Collections.synchronizedList(new ArrayList<FeatureListener>());
}
listeners.add(listener);
}
public synchronized void removeFeatureListener(FeatureListener listener) {
if (listeners == null) {
return;
}
listeners.remove(listener);
}
public ReferencedEnvelope getBounds() throws IOException {
return collection.getBounds();
}
public ReferencedEnvelope getBounds(Query query) throws IOException {
return getFeatures(query).getBounds();
}
public int getCount(Query query) throws IOException {
return getFeatures(query).size();
}
public DataAccess<SimpleFeatureType, SimpleFeature> getDataStore() {
throw new UnsupportedOperationException("CollectionFeatureSource is an inmemory wrapper");
}
public ResourceInfo getInfo() {
throw new UnsupportedOperationException("CollectionFeatureSource is an inmemory wrapper");
}
public Name getName() {
return collection.getSchema().getName();
}
public synchronized QueryCapabilities getQueryCapabilities() {
if (capabilities == null) {
capabilities = new QueryCapabilities() {
public boolean isOffsetSupported() {
return true;
}
public boolean isReliableFIDSupported() {
return true;
}
public boolean supportsSorting(org.opengis.filter.sort.SortBy[] sortAttributes) {
return true;
}
};
}
return capabilities;
}
public synchronized Set<Key> getSupportedHints() {
if (hints == null) {
Set<Key> supports = new HashSet<Key>();
// supports.add( Hints.FEATURE_DETACHED );
hints = Collections.unmodifiableSet(supports);
}
return hints;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("CollectionFeatureSource:");
buf.append(collection);
return buf.toString();
}
//
// GET FEATURES
// This forms the heart of the CollectionFeatureSource implementation
// Use: DataUtilities.mixQueries(this.query, query, "subCollection" ) as needed
//
public SimpleFeatureCollection getFeatures() throws IOException {
return getFeatures( Query.ALL );
}
public SimpleFeatureCollection getFeatures(Filter filter) {
Query query = new Query(getSchema().getTypeName(), filter);
return getFeatures(query);
}
public SimpleFeatureCollection getFeatures(Query query) {
query = DataUtilities.resolvePropertyNames(query, getSchema());
final int offset = query.getStartIndex() != null ? query.getStartIndex() : 0;
if (offset > 0 & query.getSortBy() == null) {
if (!getQueryCapabilities().supportsSorting(query.getSortBy())){
throw new IllegalStateException("Feature source does not support this sorting "
+ "so there is no way a stable paging (offset/limit) can be performed");
}
Query copy = new Query(query);
copy.setSortBy(new SortBy[] { SortBy.NATURAL_ORDER });
query = copy;
}
SimpleFeatureCollection features = collection;
// step one: filter
if( query.getFilter() != null && query.getFilter().equals(Filter.EXCLUDE)){
return new EmptyFeatureCollection( getSchema() );
}
if (query.getFilter() != null && query.getFilter() != Filter.INCLUDE) {
features = new FilteringSimpleFeatureCollection(features, query.getFilter());
}
// step two: reproject
if (query.getCoordinateSystemReproject() != null) {
features = new ReprojectingFeatureCollection(features, query
.getCoordinateSystemReproject());
}
// step two sort! (note this makes a sorted copy)
if (query.getSortBy() != null && query.getSortBy().length != 0) {
SimpleFeature array[] = features.toArray(new SimpleFeature[features.size()]);
// Arrays sort is stable (not resorting equal elements)
for (SortBy sortBy : query.getSortBy()) {
Comparator<SimpleFeature> comparator = DataUtilities.sortComparator(sortBy);
Arrays.sort(array, comparator);
}
ArrayList<SimpleFeature> list = new ArrayList<SimpleFeature>(Arrays.asList(array));
features = new ListFeatureCollection(getSchema(), list);
}
// step three skip to start and return max number of fetaures
if (offset > 0 || !query.isMaxFeaturesUnlimited()) {
long max = Long.MAX_VALUE;
if (!query.isMaxFeaturesUnlimited()) {
max = query.getMaxFeatures();
}
features = new MaxSimpleFeatureCollection(features, offset, max);
}
// step four - retyping
// (It would be nice to do this earlier so as to not have all the baggage
// of unneeded attributes)
if (query.getPropertyNames() != Query.ALL_NAMES) {
// rebuild the type and wrap the reader
SimpleFeatureType schema = features.getSchema();
SimpleFeatureType target = SimpleFeatureTypeBuilder.retype(schema, query
.getPropertyNames());
// do an equals check because we may have needlessly retyped (that is,
// the subclass might be able to only partially retype)
if (!target.equals(schema)) {
features = new ReTypingFeatureCollection(features, target);
}
}
// Wrap up the results in a method that allows subCollection
return new SubCollection( query, features );
}
/**
* SubCollection for CollectionFeatureSource.
* <p>
* Will route any calls refining the feature collection back to CollectionFeatureSource. This is
* based on the success of ContentFeatureCollection.
* </p>
*
* @author Jody
*/
protected class SubCollection extends DecoratingSimpleFeatureCollection {
private Query query;
protected SubCollection(Query query, SimpleFeatureCollection features) {
super(features);
this.query = query;
}
public SimpleFeatureCollection subCollection(Filter filter) {
Query q = new Query(getSchema().getTypeName(), filter);
Query subQuery = DataUtilities.mixQueries(query, q, q.getHandle() );
return CollectionFeatureSource.this.getFeatures( subQuery );
}
@Override
public SimpleFeatureCollection sort(SortBy order) {
Query q = new Query( getSchema().getTypeName() );
q.setSortBy( new SortBy[]{ order } );
Query subQuery = DataUtilities.mixQueries(query, q, q.getHandle() );
return CollectionFeatureSource.this.getFeatures( subQuery );
}
}
}