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 aFeatureType
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 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
Query
orTransactions
when we implemented ourCSVDataStore
. This is something thatAbstractDataStore
is handling for you and will be discussed later in the section on optimization.Query.getTypeName()
Determines which
FeatureType
is being requested. In addition,Query
supports the customizationattributes
,namespace
, andtypeName
requested from theDataStore
. While you may useDataStore.getSchema(typeName)
to retrieve the types as specified by theDataStore
, you may also create your ownFeatureType
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 theDataStore
). This capability will allow client code to use ourDataStore
with aCoordinateSystem
of their choice. The coordinates returned by theDataStore
will not be modified in any way.Query.getCoordinateSystemReproject()
Used to reproject the
Geometries
provided by aDataStore
from their original value (or the one provided byQuery.getCoordinateSystem
) into a different coordinate system. The coordinate returned by theDataStore
will be processed , either natively by AdvancedDataStore
s, 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"); 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 = 2007
DataStore.getFeatureSource(typeName)
This method is the gateway to the higher level interface as provided by an instance of
FeatureSource
,FeatureStore
orFeatureLocking
. The returned instance represents the contents of a single namedFeatureType
provided by theDataStore
. The type of the returned instance indicates the capabilities available.This far in our tutorial
CSVDataStore
will 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()
- acquireFeatureType
FeatureSource.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 aFeatureIterator
FeatureCollection.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.
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, 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:
FeatureSource
is 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.FeatureCollection
checks 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.