Using FeatureSource¶
In this part we examine the abilities of the CSVDataStore.
DataStore¶
Now that we have implemented a simple DataStore we can explore some of the capabilities made available to us.
CSVDataStore API for data access:
- DataStore.getTypeNames()
- DataStore.getSchema( typeName )
- DataStore.getFeatureReader( featureType, filter, transaction )
- DataStore.getFeatureSource( typeName )
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<>(); 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<>(); 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<>(); 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 locations.14 Seoul locations.15 Bonn close feature reader read in 15 features
Example with a quick “selection” Filter:
Map<String, Serializable> params = new HashMap<>(); params.put("file", file); DataStore store = DataStoreFinder.getDataStore(params); FilterFactory ff = CommonFactoryFinder.getFilterFactory(); Set<FeatureId> selection = new HashSet<>(); 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<>(); 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¶
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.getFeatures( query ) - request features specified by query
- FeatureSource.getFeatures( filter ) - request features based on constraints
- FeatureSource.getFeatures() - request all features
- FeatureSource.getSchema() - acquire FeatureType
- FeatureSource.getBounds() - return the bounding box of all features
- FeatureSource.getBounds( query ) - request bounding box of specified features
- FeatureSource.getCount( query ) - request number of features specified by query
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.
FeatureCollection¶
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.getSchmea()
- FeatureCollection.features() - access to a FeatureIterator
- FeatureCollection.accepts( visitor, progress )
- FeatureCollection.getBounds() - bounding box of features
- FeatureCollection.getCount() - number of features
- DataUtilities.collection( featureCollection ) - used to load features into memory
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<>(); params.put("file", file); DataStore store = DataStoreFinder.getDataStore(params); SimpleFeatureSource featureSource = store.getFeatureSource("locations"); SimpleFeatureCollection featureCollection = featureSource.getFeatures(); List<String> list = new ArrayList<>(); 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, locations.14, locations.15] FeatureSource count: 15 FeatureSource bounds: null FeatureCollection size: 15 FeatureCollection bounds: ReferencedEnvelope[-123.365556 : 151.211111, -33.925278 : 52.95] collection size: 15
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:
- FeatureSource is a way to perform a quick check for a precanned answer for count and bounds. The Shapefile format will keep this information in the header at the top of the file. In a similar fashion a Database may be able to quickly check an index for this information.
- FeatureCollection checks the contents, and possibly checks each item, for an answer to size and bounds.
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.