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 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 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 optimization.

    • 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");
            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 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 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.