Using FeatureStore

Now that we have completed our CSVDataStore implementation, we can explore the remaining capabilities of the DataStore API.

DataStore actually extends DataAccess which was introduced to allow GeoTools to work with more general feature content including properties, attributes and associations.

../../_images/DataAccess.png

DataAccess

DataStore, along with a few sub-classes, are limited to work with SimpleFeature (values only, order significant).

../../_images/DataStore.png

DataStore

CSVDataStore API for data modification:

  • CSVDataStore.createSchema(featureType)

  • CSVDataStore.getFeatureWriter(typeName, filter, Transaction)

  • CSVDataStore.getFeatureWriter(typeName, Transaction)

  • CSVDataStore.getFeatureWriterAppend(typeName, Transaction)

  • CSVDataStore.getFeatureSource(typeName)

FeatureSource

The DataStore.getFeatureSource(typeName) method is the gateway to our high level API, as provided by an instance of FeatureSource, FeatureStore or FeatureLocking.

Now that we have implemented writing operations, the result of this method supports:

  • FeatureSource: the query operations outlined in the Query Tutorial

  • FeatureStore: modification and transaction support

  • FeatureLocking: Interaction with a Feature-based Locking

FeatureStore

FeatureStore provides Transaction support and modification operations. FeatureStore is an extension of FeatureSource.

You may check the result of getFeatureSource(typeName) with the instanceof operator.

Example of FeatureStore use:

        Map<String, Serializable> params = new HashMap<>();
        params.put("file", file);
        DataStore store = DataStoreFinder.getDataStore(params);

        SimpleFeatureSource featureSource = store.getFeatureSource("locations");
        if (!(featureSource instanceof SimpleFeatureStore)) {
            throw new IllegalStateException("Modification not supported");
        }
        SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;

FeatureStore defines:

  • FeatureStore.addFeatures(featureReader)

  • FeatureStore.removeFeatures(filter)

  • FeatureStore.modifyFeatures(type, value, filter)

  • FeatureStore.modifyFeatures(types, values, filter)

  • FeatureStore.setFeatures(featureReader)

  • FeatureStore.setTransaction(transaction)

Once again, many DataStores are able to provide optimized implementations of these operations.

Transaction Example:

        Map<String, Serializable> params = new HashMap<>();
        params.put("file", file);
        DataStore store = DataStoreFinder.getDataStore(params);

        Transaction t1 = new DefaultTransaction("transaction 1");
        Transaction t2 = new DefaultTransaction("transactoin 2");

        SimpleFeatureType type = store.getSchema("locations");
        SimpleFeatureStore featureStore = (SimpleFeatureStore) store.getFeatureSource("locations");
        SimpleFeatureStore featureStore1 = (SimpleFeatureStore) store.getFeatureSource("locations");
        SimpleFeatureStore featureStore2 = (SimpleFeatureStore) store.getFeatureSource("locations");

        featureStore1.setTransaction(t1);
        featureStore2.setTransaction(t2);

        System.out.println("Step 1");
        System.out.println("------");
        System.out.println(
                "start     auto-commit: " + DataUtilities.fidSet(featureStore.getFeatures()));
        System.out.println(
                "start              t1: " + DataUtilities.fidSet(featureStore1.getFeatures()));
        System.out.println(
                "start              t2: " + DataUtilities.fidSet(featureStore2.getFeatures()));

        // select feature to remove
        FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
        Filter filter1 = ff.id(Collections.singleton(ff.featureId("fid1")));
        featureStore1.removeFeatures(filter1); // road1 removes fid1 on t1

        System.out.println();
        System.out.println("Step 2 transaction 1 removes feature 'fid1'");
        System.out.println("------");
        System.out.println(
                "t1 remove auto-commit: " + DataUtilities.fidSet(featureStore.getFeatures()));
        System.out.println(
                "t1 remove          t1: " + DataUtilities.fidSet(featureStore1.getFeatures()));
        System.out.println(
                "t1 remove          t2: " + DataUtilities.fidSet(featureStore2.getFeatures()));

        // new feature to add!
        // 42.3601° N, 71.0589° W Boston 1300 2017
        GeometryFactory gf = JTSFactoryFinder.getGeometryFactory();
        Point boston = gf.createPoint(new Coordinate(-71.0589, 42.3601));
        SimpleFeature feature =
                SimpleFeatureBuilder.build(
                        type, new Object[] {boston, "Boston", 1300, 2017}, "locations.1");
        SimpleFeatureCollection collection = DataUtilities.collection(feature);
        featureStore2.addFeatures(collection);

        System.out.println();
        System.out.println("Step 3 transaction 2 adds a new feature '" + feature.getID() + "'");
        System.out.println("------");
        System.out.println(
                "t2 add    auto-commit: " + DataUtilities.fidSet(featureStore.getFeatures()));
        System.out.println(
                "t2 add             t1: " + DataUtilities.fidSet(featureStore1.getFeatures()));
        System.out.println(
                "t1 add             t2: " + DataUtilities.fidSet(featureStore2.getFeatures()));

        // commit transaction one
        t1.commit();

        System.out.println();
        System.out.println("Step 4 transaction 1 commits the removal of feature 'fid1'");
        System.out.println("------");
        System.out.println(
                "t1 commit auto-commit: " + DataUtilities.fidSet(featureStore.getFeatures()));
        System.out.println(
                "t1 commit          t1: " + DataUtilities.fidSet(featureStore1.getFeatures()));
        System.out.println(
                "t1 commit          t2: " + DataUtilities.fidSet(featureStore2.getFeatures()));

        // commit transaction two
        t2.commit();

        System.out.println();
        System.out.println(
                "Step 5 transaction 2 commits the addition of '" + feature.getID() + "'");
        System.out.println("------");
        System.out.println(
                "t2 commit auto-commit: " + DataUtilities.fidSet(featureStore.getFeatures()));
        System.out.println(
                "t2 commit          t1: " + DataUtilities.fidSet(featureStore1.getFeatures()));
        System.out.println(
                "t2 commit          t2: " + DataUtilities.fidSet(featureStore2.getFeatures()));

        t1.close();
        t2.close();
        store.dispose(); // clear out any listeners

This produces the following output:

Step 1
------
start     auto-commit: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
start              t1: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
start              t2: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]

Step 2 transaction 1 removes feature 'fid1'
------
t1 remove auto-commit: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
t1 remove          t1: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
t1 remove          t2: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]

Step 3 transaction 2 adds a new feature 'locations.1'
------
t2 add    auto-commit: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
t2 add             t1: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
t1 add             t2: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, new0, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]

Step 4 transaction 1 commits the removal of feature 'fid1'
------
t1 commit auto-commit: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
t1 commit          t1: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
t1 commit          t2: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, new0, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]

Step 5 transaction 2 commits the addition of 'locations.1'
------
t2 commit auto-commit: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.17, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
t2 commit          t1: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.17, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]
t2 commit          t2: [locations.14, locations.13, locations.9, locations.12, locations.8, locations.11, locations.17, locations.16, locations.15, locations.3, locations.2, locations.1, locations.7, locations.6, locations.5, locations.4, locations.10]

Resulting in the following file:

LAT,LON,CITY,NUMBER,YEAR
46.066667,11.116667,Trento,140,2002
44.9441,-93.0852,St Paul,125,2003
13.752222,100.493889,Bangkok,150,2004
45.420833,-75.69,Ottawa,200,2004
44.9801,-93.251867,Minneapolis,350,2005
46.519833,6.6335,Lausanne,560,2006
48.428611,-123.365556,Victoria,721,2007
-33.925278,18.423889,Cape Town,550,2008
-33.859972,151.211111,Sydney,436,2009
41.383333,2.183333,Barcelona,914,2010
39.739167,-104.984722,Denver,869,2011
52.95,-1.133333,Nottingham,800,2013
45.52,-122.681944,Portland,840,2014
37.5667,129.681944,Seoul,473,2015
50.733992,7.099814,Bonn,700,2016
42.3601,-71.0589,Boston,800,2017
42.3601,-71.0589,Boston,1300,2017

Note

Please review the above code example carefully as it is the best explanation of transaction independence you will find.

Specifically:

  • “auto-commit” represents the current contents of the file on disk

  • Notice how the transactions only reflect the changes the user made relative to the current file contents.

    This is shown after t1 commit, where transaction t2 is seeing 4 features (i.e. the current file contents plus the one feature that has been added on t2).

  • This really shows that FeatureSource and FeatureStore are “views” into your data.

    If you configure two FeatureStores with the same transaction they will act the same.

    Transaction is important and represents what you are working on FeatureStore is not as important and is just used to make working with your data easier (or more efficient) than direct use of FeatureWriter.

FeatureLocking

FeatureLocking is the last extension to our high-level API. It provides support for preventing modifications to features for the duration of a Transaction, or a period of time.

FeatureLocking defines:

  • FeatureLocking.setFeatureLock(featureLock)

  • FeatureLocking.lockFeatures(query) - lock features specified by query

  • FeatureLocking.lockFeatures(filter) - lock according to constraints

  • FeatureLocking.lockFeatures() - lock all

  • FeatureLocking.unLockFeatures(query)

  • FeatureLocking.unLockFeatures(filter)

  • FeatureLocking.unLockFeatures()

  • FeatureLocking.releaseLock(string)

  • FeatureLocking.refreshLock(string)

The concept of a FeatureLock matches the description provided in the OGC Web Feature Server Specification. Locked Features can only be used via Transactions that have been provided with the correct authorization.

FeatureWriter

The DataStore.getFeatureWriter(typeName, filter, transaction) method creates a FeatureWriter used to modify features indicated by a constraint.

Example - removing all features:

        Map<String, Serializable> params = new HashMap<>();
        params.put("file", file);
        DataStore store = DataStoreFinder.getDataStore(params);

        Transaction t = new DefaultTransaction("locations");
        try {

            try (FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
                    store.getFeatureWriter("locations", Filter.INCLUDE, t)) {
                SimpleFeature feature;
                while (writer.hasNext()) {
                    feature = writer.next();
                    System.out.println("remove " + feature.getID());
                    writer.remove(); // marking contents for removal
                }
            }
            System.out.println("commit " + t); // now the contents are removed
            t.commit();
        } catch (Throwable eek) {
            t.rollback();
        } finally {
            t.close();
            store.dispose();
        }

This produces the following output:

remove locations.1
remove locations.2
remove locations.3
remove locations.4
remove locations.5
remove locations.6
remove locations.7
remove locations.8
remove locations.9
remove locations.10
remove locations.11
remove locations.12
remove locations.13
remove locations.14
remove locations.15
remove locations.16
commit locations

And the following file:

LAT,LON,CITY,NUMBER,YEAR

This FeatureWriter does not allow the addition of new content. It can be used for modification and removal only. DataStores can often provide an optimized implementation.

The DataStore.getFeatureWriter(typeName, transaction) method creates a general purpose FeatureWriter. New content may be added after iterating through the provided content.

Example - completely replace all features:

        Map<String, Serializable> params = new HashMap<>();
        params.put("file", file);
        DataStore store = DataStoreFinder.getDataStore(params);

        final SimpleFeatureType type = store.getSchema("locations");
        SimpleFeature f;
        DefaultFeatureCollection collection = new DefaultFeatureCollection();

        // 42.3601° N, 71.0589° W Boston 1300 2017
        GeometryFactory gf = JTSFactoryFinder.getGeometryFactory();
        Point boston = gf.createPoint(new Coordinate(-71.0589, 42.3601));
        SimpleFeature bf =
                SimpleFeatureBuilder.build(
                        type, new Object[] {boston, "Boston", 1300, 2017}, "locations.1");
        collection.add(bf);

        final FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
                store.getFeatureWriter("locations", Transaction.AUTO_COMMIT);
        try {
            // remove all features
            while (writer.hasNext()) {
                writer.next();
                writer.remove();
            }
            // copy new features in
            SimpleFeatureIterator iterator = collection.features();
            while (iterator.hasNext()) {
                SimpleFeature feature = iterator.next();
                SimpleFeature newFeature = writer.next(); // new blank feature
                newFeature.setAttributes(feature.getAttributes());
                writer.write();
            }
        } finally {
            writer.close();
        }

The modified file:

LAT,LON,CITY,NUMBER,YEAR
42.3601,-71.0589,Boston,1300,2017

The DataStore.getFeatureWriterAppend(typeName, transaction) method creates a FeatureWriter for adding content.

Example - making a copy:

        Map<String, Serializable> params = new HashMap<>();
        params.put("file", file);
        DataStore store = DataStoreFinder.getDataStore(params);
        SimpleFeatureType featureType = store.getSchema("locations");

        File file2 = new File(directory, "duplicate.rst");
        Map<String, Serializable> params2 = new HashMap<>();
        params2.put("file", file2);

        CSVDataStoreFactory factory = new CSVDataStoreFactory();
        DataStore duplicate = factory.createNewDataStore(params2);
        duplicate.createSchema(featureType);

        SimpleFeature feature, newFeature;

        Query query = new Query(featureType.getTypeName(), Filter.INCLUDE);
        FeatureReader<SimpleFeatureType, SimpleFeature> reader =
                store.getFeatureReader(query, Transaction.AUTO_COMMIT);

        FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
                duplicate.getFeatureWriterAppend("duplicate", Transaction.AUTO_COMMIT);
        // writer = duplicate.getFeatureWriter("duplicate", Transaction.AUTO_COMMIT);
        try {
            while (reader.hasNext()) {
                feature = reader.next();
                newFeature = writer.next();

                newFeature.setAttributes(feature.getAttributes());
                writer.write();
            }
        } finally {
            reader.close();
            writer.close();
        }

The modified file:

LAT,LON,CITY,NUMBER,YEAR
46.066667,11.116667,Trento,140,2002
44.9441,-93.0852,St Paul,125,2003
13.752222,100.493889,Bangkok,150,2004
45.420833,-75.69,Ottawa,200,2004
44.9801,-93.251867,Minneapolis,350,2005
46.519833,6.6335,Lausanne,560,2006
48.428611,-123.365556,Victoria,721,2007
-33.925278,18.423889,Cape Town,550,2008
-33.859972,151.211111,Sydney,436,2009
41.383333,2.183333,Barcelona,914,2010
39.739167,-104.984722,Denver,869,2011
52.95,-1.133333,Nottingham,800,2013
45.52,-122.681944,Portland,840,2014
37.5667,129.681944,Seoul,473,2015
50.733992,7.099814,Bonn,700,2016
42.3601,-71.0589,Boston,800,2017

DataStores can often provide an optimized implementation of this method.