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.
DataStore
, along with a few sub-classes, are limited to work with SimpleFeature
(values only, order
significant).
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 TutorialFeatureStore
: modification and transaction supportFeatureLocking
: 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
andFeatureStore
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 onFeatureStore
is not as important and is just used to make working with your data easier (or more efficient) than direct use ofFeatureWriter
.
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 queryFeatureLocking.lockFeatures(filter)
- lock according to constraintsFeatureLocking.lockFeatures()
- lock allFeatureLocking.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.