org.geotools.feature.DefaultFeatureCollection Maven / Gradle / Ivy
Show all versions of gt-main Show documentation
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.feature;
// J2SE interfaces
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureReader;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.feature.collection.FeatureIteratorImpl;
import org.geotools.feature.collection.SimpleFeatureIteratorImpl;
import org.geotools.feature.collection.SubFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.opengis.feature.IllegalAttributeException;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.sort.SortBy;
import org.opengis.geometry.BoundingBox;
/**
* A basic implementation of SimpleFeatureCollection which use a {@link TreeMap} for its internal
* storage.
*
* This should be considered a MemoryFeatureCollection.
*
* @author Ian Schneider
* @version $Id$
*/
public class DefaultFeatureCollection
implements SimpleFeatureCollection, Collection {
protected static Logger LOGGER =
org.geotools.util.logging.Logging.getLogger(DefaultFeatureCollection.class);
/**
* Contents of collection, referenced by FeatureID.
*
* This use will result in collections that are sorted by FID, in keeping with shapefile
* etc...
*/
private SortedMap contents = new TreeMap();
/** Internal listener storage list */
// private List listeners = new ArrayList(2);
/** Internal envelope of bounds. */
private ReferencedEnvelope bounds = null;
// private String id; /// fid
/**
* Default implementation of Feature collection.
*
* feature type determined by the first feature added.
*/
public DefaultFeatureCollection() {
this(null, null);
}
/**
* Used to stage content in memory.
*
*
Client code is encouraged to use DataUtilities.collection( collection )
*
* @param collection SimpleFeatureCollection to copy into memory
*/
public DefaultFeatureCollection(
FeatureCollection collection) {
this(collection.getID(), collection.getSchema());
addAll(collection);
}
/**
* Used to create a feature collection to stage content in memory.
*
* The feature type will be determined by the first feature added.
*
* @param id may be null ... feature id
*/
public DefaultFeatureCollection(String id) {
this(id, null);
}
/**
* Used to create a feature collection to stage content in memory.
*
* @param id may be null ... feature id
* @param memberType optional, may be null
*/
public DefaultFeatureCollection(String id, SimpleFeatureType memberType) {
this.id = id == null ? "featureCollection" : id;
this.schema = memberType;
}
/** id used when serialized to gml */
protected String id;
protected SimpleFeatureType schema;
/**
* Gets the bounding box for the features in this feature collection.
*
* @return the envelope of the geometries contained by this feature collection.
*/
public ReferencedEnvelope getBounds() {
if (bounds == null) {
bounds = new ReferencedEnvelope();
for (Iterator i = contents.values().iterator(); i.hasNext(); ) {
BoundingBox geomBounds = ((SimpleFeature) i.next()).getBounds();
// IanS - as of 1.3, JTS expandToInclude ignores "null" Envelope
// and simply adds the new bounds...
// This check ensures this behavior does not occur.
if (!geomBounds.isEmpty()) {
bounds.include(geomBounds);
}
}
}
return bounds;
}
/**
* Ensures that this collection contains the specified element (optional operation). Returns
* true if this collection changed as a result of the call. (Returns false if
* this collection does not permit duplicates and already contains the specified element.)
*
*
Collections that support this operation may place limitations on what elements may be
* added to this collection. In particular, some collections will refuse to add null
* elements, and others will impose restrictions on the type of elements that may be added.
* Collection classes should clearly specify in their documentation any restrictions on what
* elements may be added.
*
*
If a collection refuses to add a particular element for any reason other than that it
* already contains the element, it must throw an exception (rather than returning
* false). This preserves the invariant that a collection always contains the specified
* element after this call returns.
*
* @param o element whose presence in this collection is to be ensured.
* @return true if this collection changed as a result of the call
*/
public boolean add(SimpleFeature o) {
return add(o, true);
}
protected boolean add(SimpleFeature feature, boolean fire) {
// This cast is necessary to keep with the contract of Set!
if (feature == null) return false; // cannot add null!
final String ID = feature.getID();
if (ID == null) return false; // ID is required!
if (contents.containsKey(ID)) return false; // feature all ready present
if (this.schema == null) {
this.schema = feature.getFeatureType();
}
SimpleFeatureType childType = (SimpleFeatureType) getSchema();
if (!feature.getFeatureType().equals(childType)) {
LOGGER.warning("Feature Collection contains a heterogeneous" + " mix of features");
}
// Check inheritance with FeatureType here?
contents.put(ID, feature);
return true;
}
/**
* Adds all of the elements in the specified collection to this collection (optional operation).
* The behavior of this operation is undefined if the specified collection is modified while the
* operation is in progress. (This implies that the behavior of this call is undefined if the
* specified collection is this collection, and this collection is nonempty.)
*
* @param collection elements to be inserted into this collection.
* @return true if this collection changed as a result of the call
* @see #add(Object)
*/
@Override
public boolean addAll(Collection extends SimpleFeature> collection) {
// TODO check inheritance with FeatureType here!!!
boolean changed = false;
Iterator> iterator = collection.iterator();
try {
while (iterator.hasNext()) {
SimpleFeature f = (SimpleFeature) iterator.next();
boolean added = add(f, false);
changed |= added;
}
return changed;
} finally {
if (iterator instanceof FeatureIterator) {
((FeatureIterator>) iterator).close();
}
}
}
public boolean addAll(FeatureCollection, ?> collection) {
// TODO check inheritance with FeatureType here!!!
boolean changed = false;
FeatureIterator> iterator = collection.features();
try {
while (iterator.hasNext()) {
SimpleFeature f = (SimpleFeature) iterator.next();
boolean added = add(f, false);
changed |= added;
}
return changed;
} finally {
iterator.close();
}
}
/**
* Removes all of the elements from this collection (optional operation). This collection will
* be empty after this method returns unless it throws an exception.
*/
public void clear() {
contents.clear();
}
/**
* Returns true if this collection contains the specified element. More formally,
* returns true if and only if this collection contains at least one element e
* such that (o==null ? e==null : o.equals(e)).
*
* @param o element whose presence in this collection is to be tested.
* @return true if this collection contains the specified element
*/
public boolean contains(Object o) {
// The contract of Set doesn't say we have to cast here, but I think its
// useful for client sanity to get a ClassCastException and not just a
// false.
if (!(o instanceof SimpleFeature)) return false;
SimpleFeature feature = (SimpleFeature) o;
final String ID = feature.getID();
return contents.containsKey(ID); // || contents.containsValue( feature );
}
/**
* Test for collection membership.
*
* @return true if collection is completly covered
*/
public boolean containsAll(Collection> collection) {
Iterator> iterator = collection.iterator();
try {
while (iterator.hasNext()) {
SimpleFeature feature = (SimpleFeature) iterator.next();
if (!contents.containsKey(feature.getID())) {
return false;
}
}
return true;
} finally {
if (iterator instanceof FeatureIterator) {
((FeatureIterator>) iterator).close();
}
}
}
/**
* Returns true if this collection contains no elements.
*
* @return true if this collection contains no elements
*/
public boolean isEmpty() {
return contents.isEmpty();
}
/**
* Returns an iterator over the elements in this collection. There are no guarantees concerning
* the order in which the elements are returned (unless this collection is an instance of some
* class that provides a guarantee).
*
* @return an Iterator over the elements in this collection
*/
public Iterator iterator() {
// return contents.values().iterator();
final Iterator iterator = contents.values().iterator();
return new Iterator() {
SimpleFeature currFeature = null;
public boolean hasNext() {
return iterator.hasNext();
}
public SimpleFeature next() {
currFeature = (SimpleFeature) iterator.next();
return currFeature;
}
public void remove() {
iterator.remove();
bounds = null; // recalc
}
};
}
/**
* Gets a SimpleFeatureIterator of this feature collection. This allows iteration without having
* to cast.
*
* @return the SimpleFeatureIterator for this collection.
*/
public SimpleFeatureIterator features() {
return new SimpleFeatureIteratorImpl(contents.values());
}
/**
* Removes a single instance of the specified element from this collection, if it is present
* (optional operation). More formally, removes an element e such that (o==null ?
* e==null : o.equals(e)), if this collection contains one or more such elements. Returns
* true if this collection contained the specified element (or equivalently, if this collection
* changed as a result of the call).
*
* @param o element to be removed from this collection, if present.
* @return true if this collection changed as a result of the call
*/
public boolean remove(Object o) {
if (!(o instanceof SimpleFeature)) return false;
SimpleFeature f = (SimpleFeature) o;
boolean changed = contents.values().remove(f);
return changed;
}
/**
* Removes all this collection's elements that are also contained in the specified collection
* (optional operation). After this call returns, this collection will contain no elements in
* common with the specified collection.
*
* @param collection elements to be removed from this collection.
* @return true if this collection changed as a result of the call
* @see #remove(Object)
* @see #contains(Object)
*/
public boolean removeAll(Collection> collection) {
boolean changed = false;
Iterator> iterator = collection.iterator();
try {
while (iterator.hasNext()) {
SimpleFeature f = (SimpleFeature) iterator.next();
boolean removed = contents.values().remove(f);
if (removed) {
changed = true;
}
}
return changed;
} finally {
if (iterator instanceof FeatureIterator) {
((FeatureIterator>) iterator).close();
}
}
}
/**
* Retains only the elements in this collection that are contained in the specified collection
* (optional operation). In other words, removes from this collection all of its elements that
* are not contained in the specified collection.
*
* @param collection elements to be retained in this collection.
* @return true if this collection changed as a result of the call
* @see #remove(Object)
* @see #contains(Object)
*/
public boolean retainAll(Collection> collection) {
boolean modified = false;
for (Iterator> it = contents.values().iterator(); it.hasNext(); ) {
SimpleFeature f = (SimpleFeature) it.next();
if (!collection.contains(f)) {
it.remove();
modified = true;
}
}
return modified;
}
/**
* Returns the number of elements in this collection. If this collection contains more than
* Integer.MAX_VALUE elements, returns Integer.MAX_VALUE.
*
* @return the number of elements in this collection
*/
public int size() {
return contents.size();
}
/**
* Returns an array containing all of the elements in this collection. If the collection makes
* any guarantees as to what order its elements are returned by its iterator, this method must
* return the elements in the same order.
*
* The returned array will be "safe" in that no references to it are maintained by this
* collection. (In other words, this method must allocate a new array even if this collection is
* backed by an array). The caller is thus free to modify the returned array.
*
*
This method acts as bridge between array-based and collection-based APIs.
*
* @return an array containing all of the elements in this collection
*/
public Object[] toArray() {
return contents.values().toArray();
}
/**
* Returns an array containing all of the elements in this collection; the runtime type of the
* returned array is that of the specified array. If the collection fits in the specified array,
* it is returned therein. Otherwise, a new array is allocated with the runtime type of the
* specified array and the size of this collection.
*
*
If this collection fits in the specified array with room to spare (i.e., the array has
* more elements than this collection), the element in the array immediately following the end
* of the collection is set to null. This is useful in determining the length of this
* collection only if the caller knows that this collection does not contain any
* null elements.)
*
*
If this collection makes any guarantees as to what order its elements are returned by its
* iterator, this method must return the elements in the same order.
*
*
Like the toArray method, this method acts as bridge between array-based and
* collection-based APIs. Further, this method allows precise control over the runtime type of
* the output array, and may, under certain circumstances, be used to save allocation costs
*
*
Suppose l is a List known to contain only strings. The following code
* can be used to dump the list into a newly allocated array of String:
*
*
* String[] x = (String[]) v.toArray(new String[0]);
*
*
* Note that toArray(new Object[0]) is identical in function to toArray().
*
* @param a the array into which the elements of this collection are to be stored, if it is big
* enough; otherwise, a new array of the same runtime type is allocated for this purpose.
* @return an array containing the elements of this collection
*/
public T[] toArray(T[] a) {
return contents.values().toArray(a);
}
public void close(FeatureIterator close) {
if (close instanceof FeatureIteratorImpl) {
FeatureIteratorImpl wrapper = (FeatureIteratorImpl) close;
wrapper.close();
}
}
// public void close( Iterator close ) {
// // nop
// }
public FeatureReader reader() throws IOException {
@SuppressWarnings("PMD.CloseResource") // wrapped and returned
final SimpleFeatureIterator iterator = features();
return new FeatureReader() {
public SimpleFeatureType getFeatureType() {
return getSchema();
}
public SimpleFeature next()
throws IOException, IllegalAttributeException, NoSuchElementException {
return iterator.next();
}
public boolean hasNext() throws IOException {
return iterator.hasNext();
}
public void close() throws IOException {
DefaultFeatureCollection.this.close(iterator);
}
};
}
public int getCount() throws IOException {
return contents.size();
}
public SimpleFeatureCollection collection() throws IOException {
DefaultFeatureCollection copy = new DefaultFeatureCollection(null, getSchema());
List list = new ArrayList(contents.size());
SimpleFeatureIterator iterator = features();
try {
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
SimpleFeature duplicate;
try {
duplicate = SimpleFeatureBuilder.copy(feature);
} catch (IllegalAttributeException e) {
throw new DataSourceException("Unable to copy " + feature.getID(), e);
}
list.add(duplicate);
}
} finally {
iterator.close();
}
copy.addAll(list);
return copy;
}
/**
* Optimization time ... grab the fid set so other can quickly test membership during
* removeAll/retainAll implementations.
*
* @return Set of fids.
*/
public Set fids() {
return Collections.unmodifiableSet(contents.keySet());
}
public void accepts(
org.opengis.feature.FeatureVisitor visitor, org.opengis.util.ProgressListener progress)
throws IOException {
DataUtilities.visit(this, visitor, progress);
}
/**
* Will return an optimized subCollection based on access to the origional
* MemoryFeatureCollection.
*
* This method is intended in a manner similar to subList, example use:
* collection.subCollection( myFilter ).clear()
*
*
* @param filter Filter used to determine sub collection.
* @since GeoTools 2.2, Filter 1.1
*/
public SimpleFeatureCollection subCollection(Filter filter) {
if (filter == Filter.INCLUDE) {
return this;
}
return new SubFeatureCollection(this, filter);
}
/**
* Construct a sorted view of this content.
*
*
Sorts may be combined togther in a stable fashion, in congruence with the Filter 1.1
* specification.
*
*
This method should also be able to handle GeoTools specific sorting through detecting
* order as a SortBy2 instance.
*
* @since GeoTools 2.2, Filter 1.1
* @param order Filter 1.1 SortBy Construction of a Sort
* @return FeatureList sorted according to provided order
*/
public SimpleFeatureCollection sort(SortBy order) {
if (order == SortBy.NATURAL_ORDER) {
return this;
}
return null; // new OrderedFeatureList( order, compare );
}
public void purge() {
// no resources were harmed in the making of this FeatureCollection
}
public void validate() {}
public String getID() {
return id;
}
public SimpleFeatureType getSchema() {
return schema;
}
}