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
getTypeNamesprovides 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 aFeatureTypereferenced 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 org.locationtech.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 org.locationtech.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
QueryorTransactionswhen we implemented ourCSVDataStore. This is something thatAbstractDataStoreis handling for you and will be discussed later in the section on optimization.Query.getTypeName()Determines which
FeatureTypeis being requested. In addition,Querysupports the customizationattributes,namespace, andtypeNamerequested from theDataStore. While you may useDataStore.getSchema(typeName)to retrieve the types as specified by theDataStore, you may also create your ownFeatureTypeto 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
Featuresto 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 theDataStore). This capability will allow client code to use ourDataStorewith aCoordinateSystemof their choice. The coordinates returned by theDataStorewill not be modified in any way.Query.getCoordinateSystemReproject()Used to reproject the
Geometriesprovided by aDataStorefrom their original value (or the one provided byQuery.getCoordinateSystem) into a different coordinate system. The coordinate returned by theDataStorewill be processed , either natively by AdvancedDataStores, or using GeoTools reprojection routines.TransactionAllows 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"); try (FeatureReader<SimpleFeatureType, SimpleFeature> reader = datastore.getFeatureReader(query, Transaction.AUTO_COMMIT)) { 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"); }
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 locations.16 Boston close feature reader read in 16 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); try (FeatureReader<SimpleFeatureType, SimpleFeature> reader = store.getFeatureReader(query, Transaction.AUTO_COMMIT)) { 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()); } } }
Produces the following output:
feature locations.7 Location = POINT (-123.365556 48.428611) CITY = Victoria NUMBER = 721 YEAR = 2007DataStore.getFeatureSource(typeName)This method is the gateway to the higher level interface as provided by an instance of
FeatureSource,FeatureStoreorFeatureLocking. The returned instance represents the contents of a single namedFeatureTypeprovided by theDataStore. The type of the returned instance indicates the capabilities available.This far in our tutorial
CSVDataStorewill only support an instance ofFeatureSource.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 queryFeatureSource.getFeatures(filter)- request features based on constraintsFeatureSource.getFeatures()- request all featuresFeatureSource.getSchema()- acquireFeatureTypeFeatureSource.getBounds()- return the bounding box of all featuresFeatureSource.getBounds(query)- request bounding box of specified featuresFeatureSource.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 aFeatureIteratorFeatureCollection.accepts(visitor, progress)FeatureCollection.getBounds()- bounding box of featuresFeatureCollection.getCount()- number of featuresDataUtilities.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 optimized implementations that handles the above methods natively.
FeatureCollectionExample: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, locations.16] FeatureSource count: 16 FeatureSource bounds: null FeatureCollection size: 16 FeatureCollection bounds: ReferencedEnvelope[-123.365556 : 151.211111, -33.925278 : 52.95] collection size: 16
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:
FeatureSourceis a way to perform a quick check for a pre-canned 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.FeatureCollectionchecks the contents, and possibly checks each item, for an answer to size and bounds.
This is a terrible API trade-off 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.