package org.geotools.caching.grid.featurecache;
import java.io.IOException;
import java.util.ArrayList;
import junit.framework.TestCase;
import org.geotools.caching.featurecache.FeatureCache;
import org.geotools.caching.featurecache.FeatureCacheException;
import org.geotools.caching.grid.spatialindex.store.MemoryStorage;
import org.geotools.data.DataStore;
import org.geotools.data.DefaultQuery;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Transaction;
import org.geotools.data.memory.MemoryDataStore;
import org.geotools.data.memory.MemoryFeatureCollection;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.filter.FilterFactoryImpl;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultEngineeringCRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.sort.SortOrder;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
public class StreamingGridFeatureCacheTest extends TestCase {
FilterFactory filterFactory = new FilterFactoryImpl();
private DataStore rawDataset;
private FeatureCache cacheDataset;
private FeatureSource cacheFS;
private int numFeatures = 100;
protected void setUp() throws Exception {
// first
resetDatasets();
}
private void resetDatasets() throws FeatureCacheException, IOException {
SimpleFeatureType type = createFeatureType();
DefaultFeatureCollection dfc = new DefaultFeatureCollection(
"mycollection", type);
for (int i = 0; i < numFeatures; i++) {
dfc.add(makeFeature(type, 0, 500));
}
rawDataset = new MemoryDataStore(dfc);
// build up a cache
// for testing purposes limit the cache
// to 10 features.
cacheFS = rawDataset.getFeatureSource("mycollection");
ReferencedEnvelope bounds = new ReferencedEnvelope(0, 500, 0, 500, type
.getCoordinateReferenceSystem());
cacheDataset = new StreamingGridFeatureCache(cacheFS, bounds, 5000, 1000,
MemoryStorage.createInstance());
// cacheDataset = new GridFeatureCache(cacheFS, bounds, 5, 1000,
// MemoryStorage.createInstance());
}
public void testGet() throws Exception {
double x_min = 0;
double x_max = 100;
double y_min = 0;
double y_max = 100;
resetDatasets();
FilterFactory filterFactory = new FilterFactoryImpl();
FeatureType ft = rawDataset.getFeatureSource("mycollection")
.getSchema();
String localname = ft.getGeometryDescriptor().getLocalName();
String srs = ft.getGeometryDescriptor().getCoordinateReferenceSystem()
.toString();
Filter bb = filterFactory.bbox(localname, x_min, y_min, x_max, y_max,
srs);
int cnt = rawDataset.getFeatureSource("mycollection").getFeatures(bb)
.size();
assertEquals(numFeatures, rawDataset.getFeatureSource("mycollection")
.getFeatures().size());
assertEquals(cnt, cacheDataset.getFeatures(bb).size());
// this time the features should be in the cache
assertEquals(cnt, cacheDataset.getFeatures(bb).size());
}
/*
* This test is setup to test that
* the feature collection doesn't return duplicate
* features when a feature is cached
* but also returned as part of the
* feature source query.
*
*/
public void testGetDuplicateFeatures() throws FeatureCacheException,
IOException {
/*
* Create a cache with a grid that looks like this
*
*
* x = feature in the cache
*
* 10+------+-------+ | | | | xxxxxx | 5 +--x---+-------+ | x | | | | |
* 0 +------+-------+ 0 5 10
*/
//
// ---- BUILD FEATURE -----
//
SimpleFeatureTypeBuilder bb = new SimpleFeatureTypeBuilder();
bb.add("ID", Integer.class);
bb.add("the_geom", Geometry.class, DefaultEngineeringCRS.CARTESIAN_2D);
bb.setDefaultGeometry("the_geom");
bb.setName("My Feature Type");
// bb.setSRS(DefaultEngineeringCRS.CARTESIAN_2D.toString());
SimpleFeatureType featureType = bb.buildFeatureType();
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(featureType);
GeometryFactory gf = new GeometryFactory();
LineString[] geoms = new LineString[2];
int feature1[] = new int[] { 2, 4, 2, 8, 8, 8 };
int feature2[] = new int[] { 0, 0, 0, 10, 10, 10, 10, 0, 0, 0 }; // bound feature to ensure the tiles are built as shown above
Object featurescoords[] = new Object[] { feature1, feature2 };
for (int i = 0; i < featurescoords.length; i++) {
int[] values = (int[]) featurescoords[i];
Coordinate c[] = new Coordinate[values.length / 2];
for (int j = 0; j < values.length; j = j + 2) {
int x = values[j];
int y = values[j + 1];
c[j / 2] = new Coordinate(x, y);
}
geoms[i] = gf.createLineString(c);
}
SimpleFeature features[] = new SimpleFeature[geoms.length];
MemoryFeatureCollection mm = new MemoryFeatureCollection(featureType);
for (int i = 0; i < geoms.length; i++) {
features[i] = fb.buildFeature(i + "", new Object[] {
new Integer(i), geoms[i] });
mm.add(features[i]);
}
//
// ---- CACHING FEATURE COLLECTION -----
//
DataStore ds = new MemoryDataStore(mm);
FeatureSource fs = ds.getFeatureSource(featureType.getTypeName());
FeatureCache cache = new StreamingGridFeatureCache(fs, 4, 4,MemoryStorage.createInstance());
Filter upperLeft = filterFactory.bbox(featureType.getGeometryDescriptor().getLocalName(), 0, 5.1, 4.9, 9.9,featureType.getCoordinateReferenceSystem().toString());
FeatureCollection<SimpleFeatureType, SimpleFeature> fc = cache.getFeatures(upperLeft);
assertEquals(2, fc.size());
//this should end up getting the same features from the source that are already in the cache
//however we only want one copy of those feature returned;
Filter upperHalf = filterFactory.bbox(featureType.getGeometryDescriptor().getLocalName(), 0, 5.1, 9.9, 9.9,featureType.getCoordinateReferenceSystem().toString());
fc = cache.getFeatures(upperHalf);
assertEquals(2, fc.size());
}
public void testPut() throws Exception {
resetDatasets();
double x_min = 450;
double x_max = 500;
double y_min = 450;
double y_max = 500;
FilterFactory filterFactory = new FilterFactoryImpl();
FeatureType ft = rawDataset.getFeatureSource("mycollection").getSchema();
String localname = ft.getGeometryDescriptor().getLocalName();
String srs = ft.getGeometryDescriptor().getCoordinateReferenceSystem().toString();
Filter bb = filterFactory.bbox(localname, x_min, y_min, x_max, y_max,srs);
assertEquals(numFeatures, rawDataset.getFeatureSource("mycollection").getFeatures().size());
//cacheFS.resetDidRead();
assertEquals(numFeatures, cacheDataset.getFeatures().size());
//assertTrue(cacheFS.didRead());
int size = rawDataset.getFeatureSource("mycollection").getFeatures(bb).size();
//cacheFS.resetDidRead();
assertEquals(size, cacheDataset.getFeatures(bb).size());
//assertTrue(cacheFS.didRead()); // features aren't cached if all features
// are requested??
SimpleFeature newFeature = makeFeature(rawDataset.getFeatureSource("mycollection").getSchema(), 450, 500);
DefaultFeatureCollection dfc = new DefaultFeatureCollection("mycollection", rawDataset.getFeatureSource("mycollection").getSchema());
dfc.add(newFeature);
// put features which is okay but doesn't not add it back to the raw
// dataset
try{
cacheDataset.put(dfc);
}catch (Exception ex){
assertTrue(true);
}
//cacheFS.resetDidRead();
//assertEquals(size + 1, cacheDataset.getFeatures(bb).size());
//assertTrue(!cacheFS.didRead());
}
public void testWrite() {
try {
resetDatasets();
String feattype = "mycollection";
FeatureSource<SimpleFeatureType, SimpleFeature> raw = rawDataset.getFeatureSource("mycollection");
FeatureType ft = rawDataset.getFeatureSource("mycollection").getSchema();
String localname = ft.getGeometryDescriptor().getLocalName();
String srs = ft.getGeometryDescriptor().getCoordinateReferenceSystem().toString();
Filter bb = filterFactory.bbox(localname, 0, 0, 10, 10, srs);
assertEquals(numFeatures, rawDataset.getFeatureSource(feattype).getFeatures().size());
assertEquals(numFeatures, cacheDataset.getFeatures().size());
int beforebboxcnt = raw.getFeatures(bb).size();
assertEquals(beforebboxcnt, cacheDataset.getFeatures(bb).size());
SimpleFeature newFeature2 = makeFeature(raw.getSchema(), 0, 10);
Transaction t = new DefaultTransaction();
try {
FeatureWriter<SimpleFeatureType, SimpleFeature> writer = rawDataset.getFeatureWriter("mycollection", t);
while (writer.hasNext())
writer.next();
SimpleFeature feature = writer.next();
feature.setAttributes(newFeature2.getAttributes());
writer.write();
t.commit();
} finally {
t.close();
}
//cacheFS.resetDidRead();
assertEquals(numFeatures+1, cacheDataset.getFeatures().size());
//assertTrue(cacheFS.didRead());
//cacheFS.resetDidRead();
assertEquals(beforebboxcnt + 1, cacheDataset.getFeatures(bb).size());
//assertTrue(!cacheFS.didRead());
assertEquals(beforebboxcnt + 1, raw.getFeatures(bb).size());
assertEquals(numFeatures+1, raw.getFeatures().size());
assertEquals(numFeatures+1, cacheDataset.getFeatures().size());
} catch (Exception ex) {
ex.printStackTrace();
fail();
}
}
private SimpleFeatureType createFeatureType() {
SimpleFeatureTypeBuilder sb = new SimpleFeatureTypeBuilder();
sb.setName("mycollection");
sb.add("id", Integer.class);
sb.add("name", String.class);
sb.add("the_geom", Geometry.class, DefaultGeographicCRS.WGS84);
return sb.buildFeatureType();
}
private SimpleFeature makeFeature(SimpleFeatureType type, int min, int max) {
SimpleFeatureBuilder sb = new SimpleFeatureBuilder(type);
SimpleFeature sf = sb.buildFeature(null);
sf.setDefaultGeometry(createLine(min, max));
int id = (int) (Math.random() * 10000);
sf.setAttribute("id", id);
sf.setAttribute("name", "some name:" + Math.random());
return sf;
}
private LineString createLine(double min, double max) {
int numpoints = (int) (Math.random() * 100);
if (numpoints <= 1)
numpoints = 2;
Coordinate[] coords = new Coordinate[numpoints];
for (int i = 0; i < numpoints; i++) {
double x = Math.random() * (max - min) + min;
double y = Math.random() * (max - min) + min;
coords[i] = new Coordinate(x, y);
}
GeometryFactory gf = new GeometryFactory();
return gf.createLineString(coords);
}
public void testEviction() throws IOException, FeatureCacheException{
SimpleFeatureTypeBuilder bb = new SimpleFeatureTypeBuilder();
bb.add("ID", Integer.class);
bb.add("the_geom", Geometry.class, DefaultEngineeringCRS.CARTESIAN_2D);
bb.setDefaultGeometry("the_geom");
bb.setName("My Feature Type");
//bb.setSRS(DefaultEngineeringCRS.CARTESIAN_2D.toString());
SimpleFeatureType featureType = bb.buildFeatureType();
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(featureType);
GeometryFactory gf = new GeometryFactory();
LineString[] geoms = new LineString[8];
int feature1[] = new int[]{1,1, 1,2, 2,2, 2,1, 1,1};
int feature2[] = new int[]{3,3, 3,4, 4,4, 4,3, 3,3};
int feature3[] = new int[]{5,1, 5,2, 6,2, 6,1, 5,1};
int feature4[] = new int[]{7,3, 7,4, 8,4, 8,3, 7,3};
int feature5[] = new int[]{1,5, 1,6, 2,6, 2,5, 1,5};
int feature6[] = new int[]{3,7, 3,8, 4,8, 4,7, 3,7};
int feature7[] = new int[]{5,5, 5,6, 6,6, 6,5, 5,5};
int feature8[] = new int[]{7,7, 7,8, 8,8, 8,7, 7,7};
Object featurescoords[] = new Object[]{feature1, feature2, feature3, feature4, feature5, feature6, feature7, feature8};
for (int i = 0; i < featurescoords.length; i ++){
int[] values = (int[])featurescoords[i];
Coordinate c[] = new Coordinate[5];
for (int j = 0; j < values.length; j = j + 2){
int x = values[j];
int y = values[j+1];
c[j/2] = new Coordinate(x,y);
}
geoms[i] = gf.createLineString(c);
}
SimpleFeature features[] = new SimpleFeature[geoms.length];
MemoryFeatureCollection mm = new MemoryFeatureCollection(featureType);
for (int i = 0; i < geoms.length; i ++){
features[i] = fb.buildFeature(i +"", new Object[]{new Integer(i), geoms[i]});
mm.add(features[i]);
}
DataStore ds = new MemoryDataStore(mm);
FeatureCache cache = new StreamingGridFeatureCache(ds.getFeatureSource(featureType.getTypeName()), 4, 4, MemoryStorage.createInstance());
FilterFactory ff = new FilterFactoryImpl();
Filter f1 = ff.bbox("the_geom", 0, 0, 4.4, 4.4, DefaultEngineeringCRS.CARTESIAN_2D.toString());
Envelope e1 = new Envelope(0, 4.4, 0, 4.4);
Filter f2 = ff.bbox("the_geom", 0, 4.6, 4.4, 8.5, DefaultEngineeringCRS.CARTESIAN_2D.toString());
Envelope e2 = new Envelope(0, 4.4, 4.6, 8.5);
Filter f3 = ff.bbox("the_geom", 4.6, 0, 8.5, 4.4, DefaultEngineeringCRS.CARTESIAN_2D.toString());
Envelope e3 = new Envelope(4.6, 8.5, 0, 4.4);
Filter f4 = ff.bbox("the_geom", 4.6, 4.6, 8.5, 8.5, DefaultEngineeringCRS.CARTESIAN_2D.toString());
Envelope e4 = new Envelope(4.6, 8.5, 4.6, 8.5);
//there should be two features in each region
FeatureCollection fc = cache.getFeatures(f1);
assertEquals(2, fc.size());
fc = cache.getFeatures(f2);
assertEquals(2, fc.size());
fc = cache.getFeatures(f3);
assertEquals(2, fc.size());
//at this point the cache should contain feature from area 2 & 3 and features from area 1
//should have been evicted.
assertEquals(0, cache.peek(e4).size());
assertEquals(0, cache.peek(e1).size());
assertEquals(2, cache.peek(e2).size());
assertEquals(2, cache.peek(e3).size());
fc = cache.getFeatures(f4);
assertEquals(2, fc.size());
//at this point the cache should contain features from areas 3 & 4 and area 2 should
//have been evicted;
assertEquals(2, cache.peek(e3).size());
assertEquals(2, cache.peek(e4).size());
assertEquals(0, cache.peek(e2).size());
assertEquals(0, cache.peek(e1).size());
//lets query region 4 again
fc = cache.getFeatures(f4);
assertEquals(2, fc.size());
//at this point the cache should contain features from areas 3 & 4 and area 2 should
//have been evicted;
assertEquals(2, cache.peek(e3).size());
assertEquals(2, cache.peek(e4).size());
assertEquals(0, cache.peek(e2).size());
assertEquals(0, cache.peek(e1).size());
//now lets go back to 1
fc = cache.getFeatures(f1);
assertEquals(2, fc.size());
assertEquals(2, cache.peek(e4).size());
assertEquals(2, cache.peek(e1).size());
assertEquals(0, cache.peek(e2).size());
assertEquals(0, cache.peek(e3).size());
//if we peek at 4
fc = cache.peek(e4);
fc.size(); //(note area isn't actually visited until features are iterated through
//now the cache should be order (least recently used to most recently used) 1, 4
//ask for 2
//fc = cache.peek(e2);
fc = cache.getFeatures(f2);
fc.size();
//cache: 4,2
assertEquals(2, cache.peek(e4).size());
assertEquals(2, cache.peek(e2).size());
assertEquals(0, cache.peek(e1).size());
assertEquals(0, cache.peek(e3).size());
}
public void testPeek() throws FeatureCacheException, IOException {
resetDatasets();
StreamingGridFeatureCache cache = (StreamingGridFeatureCache) cacheDataset;
Filter bb = filterFactory.bbox(cacheFS.getSchema()
.getGeometryDescriptor().getLocalName(), 100, 100, 200, 200,
cacheFS.getSchema().getGeometryDescriptor()
.getCoordinateReferenceSystem().toString());
FeatureCollection fc = cache.getFeatures(bb);
int size = fc.size();
assertEquals(size, cache.peek(new Envelope(100, 200, 100, 200)).size());
assertTrue(size <= cache.peek(new Envelope(0, 1000, 0, 1000)).size()); //because of the grid sizes there may actually be more features in the cache at this point
}
/**
* A test that queries the dataset for a given set of attributes and a maximum number
* of features (Skips the geometry attribute)
* @throws FeatureCacheException
* @throws FactoryException
* @throws NoSuchAuthorityCodeException
* @throws TransformException
* @throws MismatchedDimensionException
*/
public void testQuery () throws IOException, FeatureCacheException, NoSuchAuthorityCodeException, FactoryException, MismatchedDimensionException, TransformException{
int maxfeatures = 10;
//build up query
resetDatasets();
//tests a query with a given number of attributes
//and a select number of attributes
SimpleFeatureType schema = (SimpleFeatureType)cacheDataset.getSchema();
ArrayList<String> attributes = new ArrayList<String>();
for( int i = 0; i < schema.getAttributeCount(); i++ ) {
AttributeDescriptor attr = schema.getDescriptor(i);
if( !(attr instanceof GeometryDescriptor) ){
attributes.add(attr.getName().getLocalPart());
}
}
DefaultQuery query = new DefaultQuery(schema.getTypeName(),Filter.INCLUDE, maxfeatures, attributes.toArray(new String[0]), null);
FeatureCollection features = cacheDataset.getFeatures(query);
//ensure feature count is < maximum
assertEquals(maxfeatures, features.size());
//ensure attribute count correct
SimpleFeatureType ftype = (SimpleFeatureType)features.getSchema();
assertEquals(attributes.size(), ftype.getAttributeCount());
//test a query with a different CRS
Filter f = filterFactory.bbox(schema.getGeometryDescriptor().getLocalName(), 0, 0, 100, 100, schema.getCoordinateReferenceSystem().toString());
features = cacheDataset.getFeatures(f);
int size = features.size();
CoordinateReferenceSystem targetCRS = getBCAlbers();
//same crs
query = new DefaultQuery(schema.getTypeName(), f);
query.setCoordinateSystem(schema.getCoordinateReferenceSystem());
query.setCoordinateSystemReproject(schema.getCoordinateReferenceSystem());
query.setFilter(f);
features = cacheDataset.getFeatures(query);
assertEquals(size, features.size());
//different crs
query = new DefaultQuery(schema.getTypeName(), Filter.INCLUDE);
query.setCoordinateSystem(schema.getCoordinateReferenceSystem());
query.setCoordinateSystemReproject(targetCRS);
query.setFilter(f);
features = cacheDataset.getFeatures(query);
assertEquals(targetCRS,features.getSchema().getCoordinateReferenceSystem());
assertEquals(size, features.size());
//now lets try start index
query = new DefaultQuery(schema.getTypeName());
query.setStartIndex(new Integer(4));
boolean error = false;
try{
features = cacheDataset.getFeatures(query);
}catch(IOException ex){
error = true;
}
assertTrue(error);
//sort by
String prop = schema.getAttributeDescriptors().get(1).getLocalName();
SortBy sb =filterFactory.sort(prop, SortOrder.ASCENDING);
query = new DefaultQuery(schema.getTypeName());
query.setSortBy(new SortBy[]{sb});
error = false;
try{
features = cacheDataset.getFeatures(query);
}catch (IOException ex){
error = true;
}
assertTrue(error);
}
private CoordinateReferenceSystem getBCAlbers() throws FactoryException{
String wkt = "PROJCS[\"NAD83 / BC Albers\","+
"GEOGCS[\"NAD83\", "+
" DATUM[\"North_American_Datum_1983\", "+
" SPHEROID[\"GRS 1980\", 6378137.0, 298.257222101, AUTHORITY[\"EPSG\",\"7019\"]], "+
" TOWGS84[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], "+
" AUTHORITY[\"EPSG\",\"6269\"]], "+
" PRIMEM[\"Greenwich\", 0.0, AUTHORITY[\"EPSG\",\"8901\"]], "+
" UNIT[\"degree\", 0.017453292519943295], "+
" AXIS[\"Lon\", EAST], "+
" AXIS[\"Lat\", NORTH], "+
" AUTHORITY[\"EPSG\",\"4269\"]], "+
"PROJECTION[\"Albers_Conic_Equal_Area\"], "+
"PARAMETER[\"central_meridian\", -126.0], "+
"PARAMETER[\"latitude_of_origin\", 45.0], "+
"PARAMETER[\"standard_parallel_1\", 50.0], "+
"PARAMETER[\"false_easting\", 1000000.0], "+
"PARAMETER[\"false_northing\", 0.0], "+
"PARAMETER[\"standard_parallel_2\", 58.5], "+
"UNIT[\"m\", 1.0], "+
"AXIS[\"x\", EAST], "+
"AXIS[\"y\", NORTH], "+
"AUTHORITY[\"EPSG\",\"3005\"]]";
return CRS.parseWKT(wkt);
}
}