In this part we examine the abilities of the CSVDataStore.
Now that we have implemented a simple DataStore we can explore some of the capabilities made available to us.
CSVDataStore API for data access:
If you would like to follow along with these examples you can download CSVTest.java.
DataStore.getTypeNames()
The method getTypeNames provides a list of the available types.
getTypeNames() example:
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put("file", file);
DataStore store = DataStoreFinder.getDataStore(params);
String names[] = store.getTypeNames();
System.out.println("typenames: " + names.length);
System.out.println("typename[0]: " + names[0]);
Produces the following output (given a directory with example.properties):
typenames: 1
typename[0]: locations
DataStore.getSchema( typeName )
The method getSchema( typeName ) provides access to a FeatureType referenced by a type name.
getSchema( typeName ) example:
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put("file", file);
DataStore store = DataStoreFinder.getDataStore(params);
SimpleFeatureType type = store.getSchema("locations");
System.out.println("featureType name: " + type.getName());
System.out.println("featureType count: " + type.getAttributeCount());
System.out.println("featuretype attributes list:");
// access by list
for (AttributeDescriptor descriptor : type.getAttributeDescriptors()) {
System.out.print(" " + descriptor.getName());
System.out.print(" (" + descriptor.getMinOccurs() + "," + descriptor.getMaxOccurs()
+ ",");
System.out.print((descriptor.isNillable() ? "nillable" : "manditory") + ")");
System.out.print(" type: " + descriptor.getType().getName());
System.out.println(" binding: " + descriptor.getType().getBinding().getSimpleName());
}
// access by index
AttributeDescriptor attributeDescriptor = type.getDescriptor(0);
System.out.println("attribute 0 name: " + attributeDescriptor.getName());
System.out.println("attribute 0 type: " + attributeDescriptor.getType().toString());
System.out.println("attribute 0 binding: " + attributeDescriptor.getType().getBinding());
// access by name
AttributeDescriptor cityDescriptor = type.getDescriptor("CITY");
System.out.println("attribute 'CITY' name: " + cityDescriptor.getName());
System.out.println("attribute 'CITT' type: " + cityDescriptor.getType().toString());
System.out.println("attribute 'CITY' binding: " + cityDescriptor.getType().getBinding());
// default geometry
GeometryDescriptor geometryDescriptor = type.getGeometryDescriptor();
System.out.println("default geom name: " + geometryDescriptor.getName());
System.out.println("default geom type: " + geometryDescriptor.getType().toString());
System.out.println("default geom binding: " + geometryDescriptor.getType().getBinding());
System.out.println("default geom crs: "
+ CRS.toSRS(geometryDescriptor.getCoordinateReferenceSystem()));
Produces the following output:
featureType name: locations
featureType count: 4
featuretype attributes list:
Location (0,1,nillable) type: Location binding: Point
CITY (0,1,nillable) type: CITY binding: String
NUMBER (0,1,nillable) type: NUMBER binding: String
YEAR (0,1,nillable) type: YEAR binding: String
attribute 0 name: Location
attribute 0 type: GeometryTypeImpl Location<Point>
attribute 0 binding: class com.vividsolutions.jts.geom.Point
attribute 'CITY' name: CITY
attribute 'CITT' type: AttributeTypeImpl CITY<String>
attribute 'CITY' binding: class java.lang.String
default geom name: Location
default geom type: GeometryTypeImpl Location<Point>
default geom binding: class com.vividsolutions.jts.geom.Point
default geom crs: CRS:84
DataStore.getFeatureReader( query, transaction )
The method getFeatureReader( query, transaction ) allows access to the contents of our DataStore.
The method signature may be more complicated than expected, we certainly did not talk about Query or Transactions when we implemented our CSVDataStore. This is something that AbstractDataStore is handling for you and will be discussed later in the section on optimisation.
Query.getTypeName()
Determines which FeatureType is being requested. In addition, Query supports the customization attributes, namespace, and typeName requested from the DataStore. While you may use DataStore.getSchema( typeName ) to retrieve the types as specified by the DataStore, you may also create your own FeatureType to limit the attributes returned or cast the result into a specific namespace.
Query.getFilter()
Used to define constraints on which features should be fetched. The constraints can be on spatial and non-spatial attributes of the features.
Query.getPropertiesNames()
Allows you to limit the number of properties of the returned Features to only those you are interested in.
Query.getMaxFeatures()
Defines an upper limit for the number of features returned.
Query.getHandle()
User-supplied name used to describe a query in user’s terms in any generated error messages.
Query.getCoordinateSystem()
Used to force the use of a user-supplied CoordinateSystem (rather than the one supplied by the DataStore). This capability will allow client code to use our DataStore with a CoordinateSystem of their choice. The coordinates returned by the DataStore will not be modified in any way.
Query.getCoordinateSystemReproject()
Used to reproject the Geometries provided by a DataStore from their original value (or the one provided by Query.getCoordinateSystem) into a different coordinate system. The coordinate returned by the DataStore will be processed , either natively by Advanced DataStores, or using GeoTools reprojection routines.
Transaction
Allows access the contents of a DataStore during modification.
With all of that in mind we can now proceed to our DataStore.getFeatureReader( featureType, filter, transaction ) example:
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put("file", file);
DataStore datastore = DataStoreFinder.getDataStore(params);
Query query = new Query("locations");
System.out.println("open feature reader");
FeatureReader<SimpleFeatureType, SimpleFeature> reader = datastore.getFeatureReader(query,
Transaction.AUTO_COMMIT);
try {
int count = 0;
while (reader.hasNext()) {
SimpleFeature feature = reader.next();
System.out.println(" " + feature.getID() + " " + feature.getAttribute("CITY"));
count++;
}
System.out.println("close feature reader");
System.out.println("read in " + count + " features");
} finally {
reader.close();
}
Produces the following output:
open feature reader
locations.1 Trento
locations.2 St Paul
locations.3 Bangkok
locations.4 Ottawa
locations.5 Minneapolis
locations.6 Lausanne
locations.7 Victoria
locations.8 Cape Town
locations.9 Sydney
locations.10 Barcelona
locations.11 Denver
locations.12 Nottingham
locations.13 Portland
close feature reader
read in 13 features
Example with a quick “selection” Filter:
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put("file", file);
DataStore store = DataStoreFinder.getDataStore(params);
FilterFactory ff = CommonFactoryFinder.getFilterFactory();
Set<FeatureId> selection = new HashSet<FeatureId>();
selection.add(ff.featureId("locations.7"));
Filter filter = ff.id(selection);
Query query = new Query("locations", filter);
FeatureReader<SimpleFeatureType, SimpleFeature> reader = store.getFeatureReader(query,
Transaction.AUTO_COMMIT);
try {
while (reader.hasNext()) {
SimpleFeature feature = reader.next();
System.out.println("feature " + feature.getID());
for (Property property : feature.getProperties()) {
System.out.print("\t");
System.out.print(property.getName());
System.out.print(" = ");
System.out.println(property.getValue());
}
}
} finally {
reader.close();
}
Produces the following output:
feature locations.7
Location = POINT (-123.365556 48.428611)
CITY = Victoria
NUMBER = 721
YEAR = 2007
DataStore.getFeatureSource( typeName )
This method is the gateway to the higher level interface as provided by an instance of FeatureSource, FeatureStore or FeatureLocking. The returned instance represents the contents of a single named FeatureType provided by the DataStore. The type of the returned instance indicates the capabilities available.
This far in our tutorial CSVDataStore will only support an instance of FeatureSource.
Example getFeatureSource:
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put("file", file);
DataStore store = DataStoreFinder.getDataStore(params);
SimpleFeatureSource featureSource = store.getFeatureSource("locations");
Filter filter = CQL.toFilter("CITY = 'Denver'");
SimpleFeatureCollection features = featureSource.getFeatures(filter);
System.out.println("found :" + features.size() + " feature");
SimpleFeatureIterator iterator = features.features();
try {
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
Geometry geometry = (Geometry) feature.getDefaultGeometry();
System.out.println(feature.getID() + " default geometry " + geometry);
}
} catch (Throwable t) {
iterator.close();
}
Producing the following output:
found :1 feature
locations.11 default geometry POINT (-104.984722 39.739167)
FeatureSource provides the ability to query a DataStore and represents the contents of a single FeatureType. In our example, the PropertiesDataStore represents a directory full of properties files. FeatureSource will represent a single one of those files.
FeatureSource defines:
FeatureSource also defines an event notification system and provides access to the DataStore which created it. You may have more than one FeatureSource operating against a file at any time.
While the FeatureSource API does allow you to represent a named FeatureType, it still does not allow direct access to a FeatureReader. The getFeatures methods actually return an instance of FeatureCollection.
FeatureCollection defines:
FeatureCollection is the closest thing we have to a prepared request. Many DataStores are able to provide optimised implementations that handles the above methods natively.
FeatureCollection Example:
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put("file", file);
DataStore store = DataStoreFinder.getDataStore(params);
SimpleFeatureSource featureSource = store.getFeatureSource("locations");
SimpleFeatureCollection featureCollection = featureSource.getFeatures();
List<String> list = new ArrayList<String>();
try (SimpleFeatureIterator features = featureCollection.features();) {
while (features.hasNext()) {
list.add(features.next().getID());
}
} // try-with-resource will call features.close()
System.out.println(" List Contents: " + list);
System.out.println(" FeatureSource count: " + featureSource.getCount(Query.ALL));
System.out.println(" FeatureSource bounds: " + featureSource.getBounds(Query.ALL));
System.out.println("FeatureCollection size: " + featureCollection.size());
System.out.println("FeatureCollection bounds: " + featureCollection.getBounds());
// Load into memory!
DefaultFeatureCollection collection = DataUtilities.collection(featureCollection);
System.out.println(" collection size: " + collection.size());
With the following output:
List Contents: [locations.1, locations.2, locations.3, locations.4, locations.5, locations.6, locations.7, locations.8, locations.9, locations.10, locations.11, locations.12, locations.13]
FeatureSource count: 13
FeatureSource bounds: null
FeatureCollection size: 13
FeatureCollection bounds: ReferencedEnvelope[-123.365556 : 151.211111, -33.925278 : 52.95]
collection size: 13
Note
Warning: When calling FeatureSource.count(Query.ALL) be aware a DataStore implementation may return -1 indicating that the value is too expensive for the DataStore to calculate.
You can think of this as:
This is a terrible API tradeoff to have to make, resulting from implementations taking ten minutes to performing a “full table scan”.
Care should be taken when using the collection() method to capture the contents of a DataStore in memory. GIS applications often produce large volumes of information and can place a strain on memory use.