package org.geotools.caching.grid.featurecache; import java.io.IOException; import java.util.Collection; import java.util.Iterator; import java.util.logging.Level; import org.geotools.caching.CacheOversizedException; import org.geotools.caching.featurecache.FeatureCacheException; import org.geotools.caching.grid.featurecache.readers.GridCachingFeatureCollection; import org.geotools.caching.grid.spatialindex.NodeLockInvalidatingVisitor; import org.geotools.caching.spatialindex.NodeIdentifier; import org.geotools.caching.spatialindex.Storage; import org.geotools.caching.util.BBoxFilterSplitter; import org.geotools.caching.util.CacheUtil; import org.geotools.data.DefaultQuery; import org.geotools.data.Query; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.store.EmptyFeatureCollection; import org.geotools.feature.FeatureCollection; import org.geotools.geometry.jts.ReferencedEnvelope; import org.opengis.filter.Filter; import org.opengis.filter.spatial.BBOX; import com.vividsolutions.jts.geom.Envelope; /** * Implementation of a GridFeatureCache that uses * streaming feature collections. Feature are streamed out of the cache * and the feature source only when requested. Features streams out of the * feature source are cached as they are read. * * <p>This is a read only implementation; you cannot add * features to this cache.</p> * <p>This implementation of a feature cache locks the cache at the node level * not the entire cache. So as long as you are accessing different nodes you should * be able to write to and read from the cache at the same time. * </p> * <p>Only supports SimpleFeatureType & SimpleFeature</p> * * @author Emily * @since 1.2.0 * * * * @source $URL$ */ public class StreamingGridFeatureCache extends GridFeatureCache { public StreamingGridFeatureCache(SimpleFeatureSource fs, int indexcapacity, int capacity, Storage store) throws FeatureCacheException { super(fs, indexcapacity, capacity, store); } public StreamingGridFeatureCache(SimpleFeatureSource fs, ReferencedEnvelope env, int indexcapacity, int capacity, Storage store){ super(fs, env, indexcapacity, capacity, store); } /** * Gets all feature within a given envelope. */ @Override public SimpleFeatureCollection get(Envelope e) throws IOException{ Filter f = ff.bbox(getSchema().getGeometryDescriptor().getLocalName(), e.getMinX(), e.getMinY(), e.getMaxX(), e.getMaxY(), getSchema().getCoordinateReferenceSystem().toString()); return getFeatures(f); } /** * Gets all features that match a query. * * <p>Currently the query handle is not supported.</p> */ @Override public SimpleFeatureCollection getFeatures(Query query) throws IOException{ //setup types if (query.getTypeName() == null){ query = new DefaultQuery(query); ((DefaultQuery)query).setTypeName(this.getSchema().getTypeName()); }else if (!query.getTypeName().equals(this.getSchema().getTypeName())){ //types do not match return new EmptyFeatureCollection(this.getSchema()); } Filter[] filters = splitFilter(query.getFilter()); Filter envFilter = filters[0]; //Filter postFilter = filters[1]; if (envFilter.equals(Filter.EXCLUDE)){ //nothing to get return new EmptyFeatureCollection(this.getSchema()); }else if (envFilter.equals(Filter.INCLUDE)){ //lets create a new bbox filter out of the bounds envFilter = createFilterFromBounds(); return new GridCachingFeatureCollection((BBOX)envFilter, query, this, this.fs, true); }else if (envFilter instanceof BBOX){ return new GridCachingFeatureCollection((BBOX)envFilter, query, this, this.fs, true); }else{ throw new UnsupportedOperationException("Invalid filter created."); } } /** * Get all features that match given filter. */ @Override public SimpleFeatureCollection getFeatures(Filter filter) throws IOException{ Filter[] filters = splitFilter(filter); Filter envFilter = filters[0]; if (envFilter.equals(Filter.EXCLUDE)){ //nothing to get return new EmptyFeatureCollection(this.getSchema()); }else if (envFilter.equals(Filter.INCLUDE)) { //get bounds of data envFilter = createFilterFromBounds(); return generateFeatureCollection((BBOX)envFilter, filter); } else if (envFilter instanceof BBOX){ SimpleFeatureCollection fc = generateFeatureCollection((BBOX)envFilter, filter); return fc; }else{ throw new UnsupportedOperationException("Invalid filter created."); } } /** * Creates a bbox filter from the data bounds * @return * @throws IOException */ private BBOX createFilterFromBounds() throws IOException{ Envelope env = this.getBounds(); BBOX myfilter = ff.bbox(getSchema().getGeometryDescriptor().getLocalName(), env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY(), getSchema().getCoordinateReferenceSystem().toString()); return myfilter; } /** * Creates a streaming feature collection * * @param envFilter * @param postFilter * @return */ private SimpleFeatureCollection generateFeatureCollection(BBOX envFilter, Filter postFilter){ GridCachingFeatureCollection collection = new GridCachingFeatureCollection(envFilter, postFilter, this, this.fs, true); return collection; } /** * Creates a streaming feature collection * * @param envFilter * @param postFilter * @return */ private SimpleFeatureCollection generateFeatureCollection(Envelope env){ GridCachingFeatureCollection collection = new GridCachingFeatureCollection(env, this); return collection; } /** * Splits the filter into two parts; the "bounds" part which * will be used to get features from the cache; the filter part which * will be applied after to ensure the feature meets the entire * filter needs. * * * @param filter */ private Filter[] splitFilter(Filter filter){ BBoxFilterSplitter splitter = new BBoxFilterSplitter(); filter.accept(splitter, null); Filter spatial_restrictions = splitter.getFilterPre(); Filter other_restrictions = splitter.getFilterPost(); return new Filter[]{spatial_restrictions, other_restrictions}; } /** * Returns all features in the cache that match the given envelope. */ public SimpleFeatureCollection peek(Envelope e) { return generateFeatureCollection(e); } /** * Unsupported. */ public void put(FeatureCollection fc, Envelope e) throws CacheOversizedException { throw new UnsupportedOperationException("Streaming feature cache does not put entire feature collections."); } /** * Unsupported. */ public void put(FeatureCollection fc) throws CacheOversizedException { throw new UnsupportedOperationException("Streaming feature cache does not put entire feature collections."); } /** * Removes all nodes from the cache that match the given envelope. */ public void remove(Envelope e) { NodeLockInvalidatingVisitor v = new NodeLockInvalidatingVisitor(this.tracker); try { if (e == null) { e = getBounds();// no envelope specified so assume everything } this.tracker.intersectionQuery(CacheUtil.convert(e), v); // invalidates nodes this.tracker.getStatistics().addToDataCounter(-v.getDataCount()); //update stats to remove all data } catch (IOException ex) { logger.log(Level.SEVERE, "Error removing elements from cache.", ex); } this.tracker.flush(); } /** * Registers a collection of nodes as valid. * This function assumes that the necessary nodes are locked * so that synchronization problems don't occur. * @param nodes */ public void register( Collection<NodeIdentifier> nodes ) { // we don't want to track access while we register; this will ensure that just because a // node touches another node it // isn't considered an access; it might be better to improve the way containment query is // done. boolean recordaccess = this.tracker.getDoRecordAccess(); try { this.tracker.setDoRecordAccess(false); for( Iterator<NodeIdentifier> iterator = nodes.iterator(); iterator.hasNext(); ) { NodeIdentifier nodeIdentifier = (NodeIdentifier) iterator.next(); nodeIdentifier.setValid(true); } } finally { this.tracker.setDoRecordAccess(recordaccess); } } /** * Un-registers a collection of nodes. * This function assumes that the necessary nodes are locked * so that synchronization problems don't occur. * * @param nodes */ @Override public void unregister( Collection<NodeIdentifier> nodes ) { // we don't want to track access while we register; this will ensure that just because a // node touches another node it // isn't considered an access; it might be better to improve the way containment query is // done. boolean recordaccess = this.tracker.getDoRecordAccess(); try { this.tracker.setDoRecordAccess(false); for( Iterator<NodeIdentifier> iterator = nodes.iterator(); iterator.hasNext(); ) { NodeIdentifier nodeIdentifier = (NodeIdentifier) iterator.next(); nodeIdentifier.setValid(false); } } finally { this.tracker.setDoRecordAccess(recordaccess); } } }