package mil.nga.giat.geowave.examples.query;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.mock.MockInstance;
import org.apache.accumulo.core.client.security.tokens.PasswordToken;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import mil.nga.giat.geowave.adapter.vector.FeatureDataAdapter;
import mil.nga.giat.geowave.adapter.vector.query.cql.CQLQuery;
import mil.nga.giat.geowave.core.geotime.GeometryUtils;
import mil.nga.giat.geowave.core.geotime.ingest.SpatialDimensionalityTypeProvider.SpatialIndexBuilder;
import mil.nga.giat.geowave.core.geotime.store.query.SpatialQuery;
import mil.nga.giat.geowave.core.index.ByteArrayId;
import mil.nga.giat.geowave.core.store.CloseableIterator;
import mil.nga.giat.geowave.core.store.DataStore;
import mil.nga.giat.geowave.core.store.IndexWriter;
import mil.nga.giat.geowave.core.store.adapter.AdapterStore;
import mil.nga.giat.geowave.core.store.index.PrimaryIndex;
import mil.nga.giat.geowave.core.store.query.QueryOptions;
import mil.nga.giat.geowave.datastore.accumulo.AccumuloDataStore;
import mil.nga.giat.geowave.datastore.accumulo.BasicAccumuloOperations;
import mil.nga.giat.geowave.datastore.accumulo.metadata.AccumuloAdapterStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is intended to provide a few examples on running Geowave queries
* of different types: 1- Querying by polygon a set of points. 2- Filtering on
* attributes of features using CQL queries 3- Ingesting polygons, and running
* polygon intersect queries. You can check all points, geometries and query
* accuracy in a more visual manner @ http://geojson.io/
*/
public class SpatialQueryExample
{
private static Logger log = LoggerFactory.getLogger(SpatialQueryExample.class);
// We'll use GeoWave's VectorDataStore, which allows to run CQL rich queries
private static DataStore dataStore;
// We need the AccumuloAdapterStore, which keeps a registry of adapter-ids,
// used to be able to query specific "tables" or "types" of features.
private static AdapterStore adapterStore;
public static void main(
String[] args )
throws AccumuloSecurityException,
AccumuloException,
ParseException,
CQLException,
IOException {
SpatialQueryExample example = new SpatialQueryExample();
log.info("Setting up datastores");
example.setupDataStores();
log.info("Running point query examples");
example.runPointExamples();
log.info("Running polygon query examples");
example.runPolygonExamples();
}
private static void setupDataStores()
throws AccumuloSecurityException,
AccumuloException {
// Initialize VectorDataStore and AccumuloAdapterStore
MockInstance instance = new MockInstance();
// For the MockInstance we can user "user" - "password" as our
// connection tokens
Connector connector = instance.getConnector(
"user",
new PasswordToken(
"password"));
BasicAccumuloOperations operations = new BasicAccumuloOperations(
connector);
dataStore = new AccumuloDataStore(
operations);
adapterStore = new AccumuloAdapterStore(
operations);
}
/**
* We'll run our point related operations. The data ingested and queried is
* single point based, meaning the index constructed will be based on a
* point.
*/
private void runPointExamples()
throws ParseException,
CQLException,
IOException {
ingestPointData();
pointQueryCase1();
pointQueryCase2();
pointQueryCase3();
pointQueryCase4();
}
private void ingestPointData() {
log.info("Ingesting point data");
ingestPointBasicFeature();
ingestPointComplexFeature();
log.info("Point data ingested");
}
private void ingest(
FeatureDataAdapter adapter,
PrimaryIndex index,
List<SimpleFeature> features ) {
try (IndexWriter indexWriter = dataStore.createWriter(
adapter,
index)) {
for (SimpleFeature sf : features) {
//
indexWriter.write(sf);
}
}
catch (IOException e) {
log.error(
"Could not create writter",
e);
}
}
private void ingestPointBasicFeature() {
// First, we'll build our first kind of SimpleFeature, which we'll call
// "basic-feature"
// We need the type builder to build the feature type
SimpleFeatureTypeBuilder sftBuilder = new SimpleFeatureTypeBuilder();
// AttributeTypeBuilder for the attributes of the SimpleFeature
AttributeTypeBuilder attrBuilder = new AttributeTypeBuilder();
// Here we're setting the SimpleFeature name. Later on, we'll be able to
// query GW just by this particular feature.
sftBuilder.setName("basic-feature");
// Add the attributes to the feature
// Add the geometry attribute, which is mandatory for GeoWave to be able
// to construct an index out of the SimpleFeature
sftBuilder.add(attrBuilder.binding(
Point.class).nillable(
false).buildDescriptor(
"geometry"));
// Add another attribute just to be able to filter by it in CQL
sftBuilder.add(attrBuilder.binding(
String.class).nillable(
false).buildDescriptor(
"filter"));
// Create the SimpleFeatureType
SimpleFeatureType sfType = sftBuilder.buildFeatureType();
// We need the adapter for all our operations with GeoWave
FeatureDataAdapter sfAdapter = new FeatureDataAdapter(
sfType);
// Now we build the actual features. We'll create two points.
// First point
SimpleFeatureBuilder sfBuilder = new SimpleFeatureBuilder(
sfType);
sfBuilder.set(
"geometry",
GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(
-80.211181640625,
25.848101000701597)));
sfBuilder.set(
"filter",
"Basic-Stadium");
// When calling buildFeature, we need to pass an unique id for that
// feature, or it will be overwritten.
SimpleFeature basicPoint1 = sfBuilder.buildFeature("1");
// Construct the second feature.
sfBuilder.set(
"geometry",
GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(
-80.191360,
25.777804)));
sfBuilder.set(
"filter",
"Basic-College");
SimpleFeature basicPoint2 = sfBuilder.buildFeature("2");
ArrayList<SimpleFeature> features = new ArrayList<SimpleFeature>();
features.add(basicPoint1);
features.add(basicPoint2);
// Ingest the data. For that purpose, we need the feature adapter,
// the index type (the default spatial index is used here),
// and an iterator of SimpleFeature
ingest(
sfAdapter,
new SpatialIndexBuilder().createIndex(),
features);
}
/**
* We're going to ingest a more complete simple feature.
*/
private void ingestPointComplexFeature() {
// First, we'll build our second kind of SimpleFeature, which we'll call
// "complex-feature"
// We need the type builder to build the feature type
SimpleFeatureTypeBuilder sftBuilder = new SimpleFeatureTypeBuilder();
// AttributeTypeBuilder for the attributes of the SimpleFeature
AttributeTypeBuilder attrBuilder = new AttributeTypeBuilder();
// Here we're setting the SimpleFeature name. Later on, we'll be able to
// query GW just by this particular feature.
sftBuilder.setName("complex-feature");
// Add the attributes to the feature
// Add the geometry attribute, which is mandatory for GeoWave to be able
// to construct an index out of the SimpleFeature
sftBuilder.add(attrBuilder.binding(
Point.class).nillable(
false).buildDescriptor(
"geometry"));
// Add another attribute just to be able to filter by it in CQL
sftBuilder.add(attrBuilder.binding(
String.class).nillable(
false).buildDescriptor(
"filter"));
// Add more attributes to use with CQL filtering later on.
sftBuilder.add(attrBuilder.binding(
Double.class).nillable(
false).buildDescriptor(
"latitude"));
sftBuilder.add(attrBuilder.binding(
Double.class).nillable(
false).buildDescriptor(
"longitude"));
// Create the SimpleFeatureType
SimpleFeatureType sfType = sftBuilder.buildFeatureType();
// We need the adapter for all our operations with GeoWave
FeatureDataAdapter sfAdapter = new FeatureDataAdapter(
sfType);
// Now we build the actual features. We'll create two more points.
// First point
SimpleFeatureBuilder sfBuilder = new SimpleFeatureBuilder(
sfType);
sfBuilder.set(
"geometry",
GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(
-80.193388,
25.780538)));
sfBuilder.set(
"filter",
"Complex-Station");
sfBuilder.set(
"latitude",
25.780538);
sfBuilder.set(
"longitude",
-80.193388);
// When calling buildFeature, we need to pass an unique id for that
// feature, or it will be overwritten.
SimpleFeature basicPoint1 = sfBuilder.buildFeature("1");
// Construct the second feature.
sfBuilder.set(
"geometry",
GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(
-118.26713562011719,
33.988349152677955)));
sfBuilder.set(
"filter",
"Complex-LA");
sfBuilder.set(
"latitude",
33.988349152677955);
sfBuilder.set(
"longitude",
-118.26713562011719);
SimpleFeature basicPoint2 = sfBuilder.buildFeature("2");
ArrayList<SimpleFeature> features = new ArrayList<SimpleFeature>();
features.add(basicPoint1);
features.add(basicPoint2);
// Ingest the data. For that purpose, we need the feature adapter,
// the index type (the default spatial index is used here),
// and an iterator of SimpleFeature
ingest(
sfAdapter,
new SpatialIndexBuilder().createIndex(),
features);
/**
* After ingest, a single point might look like this in Accumulo.
*/
// \x1F\x11\xCB\xFC\xB6\xEFT\x00\xFFcomplex_feature4\x00\x00\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x00
// complex_feature:filter [] Complex-LA
// \x1F\x11\xCB\xFC\xB6\xEFT\x00\xFFcomplex_feature4\x00\x00\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x00
// complex_feature:geom\x00\x00 []
// \x00\x00\x00\x00\x01\xC0]\x91\x18\xC0\x00\x00\x00@@\xFE\x829\x9B\xE3\xFC
// \x1F\x11\xCB\xFC\xB6\xEFT\x00\xFFcomplex_feature4\x00\x00\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x00
// complex_feature:latitude [] @@\xFE\x829\x9B\xE3\xFC
// \x1F\x11\xCB\xFC\xB6\xEFT\x00\xFFcomplex_feature\x00\x00\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x00
// complex_feature:longitude [] \xC0]\x91\x18\xC0\x00\x00\x
}
/**
* This query will search all points using the world's Bounding Box
*/
private void pointQueryCase1()
throws ParseException,
IOException {
log.info("Running Point Query Case 1");
// First, we need to obtain the adapter for the SimpleFeature we want to
// query.
// We'll query basic-feature in this example.
// Obtain adapter for our "basic-feature" type
ByteArrayId bfAdId = new ByteArrayId(
"basic-feature");
FeatureDataAdapter bfAdapter = (FeatureDataAdapter) adapterStore.getAdapter(bfAdId);
// Define the geometry to query. We'll find all points that fall inside
// that geometry
String queryPolygonDefinition = "POLYGON (( " + "-180 -90, " + "-180 90, " + "180 90, " + "180 -90, "
+ "-180 -90" + "))";
Geometry queryPolygon = new WKTReader(
JTSFactoryFinder.getGeometryFactory()).read(queryPolygonDefinition);
// Perform the query.Parameters are
/**
* 1- Adapter previously obtained from the feature name. 2- Default
* spatial index. 3- A SpatialQuery, which takes the query geometry -
* aka Bounding box 4- Filters. For this example, no filter is used. 5-
* Limit. Same as standard SQL limit. 0 is no limits. 6- Accumulo
* authorizations. For our mock instances, "root" works. In a real
* Accumulo setting, whatever authorization is associated to the user in
* question.
*/
final QueryOptions options = new QueryOptions(
bfAdapter,
new SpatialIndexBuilder().createIndex());
options.setAuthorizations(new String[] {
"root"
});
int count = 0;
try (final CloseableIterator<SimpleFeature> iterator = dataStore.query(
options,
new SpatialQuery(
queryPolygon))) {
while (iterator.hasNext()) {
SimpleFeature sf = iterator.next();
log.info("Obtained SimpleFeature " + sf.getName().toString() + " - " + sf.getAttribute("filter"));
count++;
System.out.println("Query match: " + iterator.next().getID());
}
log.info("Should have obtained 2 features. -> " + (count == 2));
}
}
/**
* This query will use a specific Bounding Box, and will find only 1 point.
*/
private void pointQueryCase2()
throws ParseException,
IOException {
log.info("Running Point Query Case 2");
// First, we need to obtain the adapter for the SimpleFeature we want to
// query.
// We'll query complex-feature in this example.
// Obtain adapter for our "complex-feature" type
ByteArrayId bfAdId = new ByteArrayId(
"complex-feature");
FeatureDataAdapter bfAdapter = (FeatureDataAdapter) adapterStore.getAdapter(bfAdId);
// Define the geometry to query. We'll find all points that fall inside
// that geometry.
String queryPolygonDefinition = "POLYGON (( " + "-118.50059509277344 33.75688594085081, "
+ "-118.50059509277344 34.1521587488017, " + "-117.80502319335938 34.1521587488017, "
+ "-117.80502319335938 33.75688594085081, " + "-118.50059509277344 33.75688594085081" + "))";
Geometry queryPolygon = new WKTReader(
JTSFactoryFinder.getGeometryFactory()).read(queryPolygonDefinition);
// Perform the query.Parameters are
/**
* 1- Adapter previously obtained from the feature name. 2- Default
* spatial index. 3- A SpatialQuery, which takes the query geometry -
* aka Bounding box 4- Filters. For this example, no filter is used. 5-
* Limit. Same as standard SQL limit. 0 is no limits. 6- Accumulo
* authorizations. For our mock instances, "root" works. In a real
* Accumulo setting, whatever authorization is associated to the user in
* question.
*/
final QueryOptions options = new QueryOptions(
bfAdapter,
new SpatialIndexBuilder().createIndex(),
new String[] {
"root"
});
int count = 0;
try (final CloseableIterator<SimpleFeature> iterator = dataStore.query(
options,
new SpatialQuery(
queryPolygon))) {
while (iterator.hasNext()) {
SimpleFeature sf = iterator.next();
log.info("Obtained SimpleFeature " + sf.getName().toString() + " - " + sf.getAttribute("filter"));
count++;
System.out.println("Query match: " + sf.getID());
}
log.info("Should have obtained 1 feature. -> " + (count == 1));
}
}
/**
* This query will use the world's Bounding Box together with a CQL filter.
*/
private void pointQueryCase3()
throws ParseException,
CQLException,
IOException {
log.info("Running Point Query Case 3");
// First, we need to obtain the adapter for the SimpleFeature we want to
// query.
// We'll query basic-feature in this example.
// Obtain adapter for our "basic-feature" type
ByteArrayId bfAdId = new ByteArrayId(
"basic-feature");
FeatureDataAdapter bfAdapter = (FeatureDataAdapter) adapterStore.getAdapter(bfAdId);
String CQLFilter = "filter = 'Basic-Stadium'";
// Perform the query.Parameters are
/**
* 1- Adapter previously obtained from the feature name. 2- Default
* spatial index. 3- A SpatialQuery, which takes the query geometry -
* aka Bounding box 4- Filters. For this example, we reduce all returned
* points (2) by using a filter. 5- Limit. Same as standard SQL limit. 0
* is no limits. 6- Accumulo authorizations. For our mock instances,
* "root" works. In a real Accumulo setting, whatever authorization is
* associated to the user in question.
*/
final QueryOptions options = new QueryOptions(
bfAdapter,
new SpatialIndexBuilder().createIndex(),
new String[] {
"root"
});
int count = 0;
try (final CloseableIterator<SimpleFeature> iterator = dataStore.query(
options,
CQLQuery.createOptimalQuery(
CQLFilter,
bfAdapter,
options.getIndex()))) {
// Our query would have found 2 points based only on the Bounding
// Box, but using the
// filter to match a particular attribute will reduce our result set
// size to 1
while (iterator.hasNext()) {
SimpleFeature sf = iterator.next();
log.info("Obtained SimpleFeature " + sf.getName().toString() + " - " + sf.getAttribute("filter"));
count++;
System.out.println("Query match: " + sf.getID());
}
log.info("Should have obtained 1 feature. " + (count == 1));
}
}
/**
* This query will use the world's Bounding Box together with a more complex
* CQL filter.
*/
private void pointQueryCase4()
throws ParseException,
CQLException,
IOException {
log.info("Running Point Query Case 4");
// First, we need to obtain the adapter for the SimpleFeature we want to
// query.
// We'll query complex-feature in this example.
// Obtain adapter for our "complex-feature" type
ByteArrayId bfAdId = new ByteArrayId(
"complex-feature");
FeatureDataAdapter bfAdapter = (FeatureDataAdapter) adapterStore.getAdapter(bfAdId);
// This CQL query will yield a single point - Complex-LA
String CQLFilter = "latitude > 25 AND longitude < -118";
// Perform the query.Parameters are
/**
* 1- Adapter previously obtained from the feature name. 2- Default
* spatial index. 3- A SpatialQuery, which takes the query geometry -
* aka Bounding box 4- Filters. For this example, we reduce all returned
* points (2) by using a filter. 5- Limit. Same as standard SQL limit. 0
* is no limits. 6- Accumulo authorizations. For our mock instances,
* "root" works. In a real Accumulo setting, whatever authorization is
* associated to the user in question.
*/
final QueryOptions options = new QueryOptions(
bfAdapter,
new SpatialIndexBuilder().createIndex(),
new String[] {
"root"
});
int count = 0;
try (final CloseableIterator<SimpleFeature> iterator = dataStore.query(
options,
CQLQuery.createOptimalQuery(
CQLFilter,
bfAdapter,
options.getIndex()))) {
// Our query would have found 2 points based only on the Bounding
// Box, but using the
// filter to match a particular attribute will reduce our result set
// size to 1
while (iterator.hasNext()) {
SimpleFeature sf = iterator.next();
log.info("Obtained SimpleFeature " + sf.getName().toString() + " - " + sf.getAttribute("filter"));
count++;
System.out.println("Query match: " + sf.getID());
}
log.info("Should have obtained 1 feature. -> " + (count == 1));
}
}
/**
* We'll run our polygon related operations. The data ingested and queried
* is single polygon based, meaning the index constructed will be based on a
* Geometry.
*/
private void runPolygonExamples()
throws ParseException,
IOException {
ingestPolygonFeature();
polygonQueryCase1();
}
private void ingestPolygonFeature()
throws ParseException {
log.info("Ingesting polygon data");
// First, we'll build our third kind of SimpleFeature, which we'll call
// "polygon-feature"
// We need the type builder to build the feature type
SimpleFeatureTypeBuilder sftBuilder = new SimpleFeatureTypeBuilder();
// AttributeTypeBuilder for the attributes of the SimpleFeature
AttributeTypeBuilder attrBuilder = new AttributeTypeBuilder();
// Here we're setting the SimpleFeature name. Later on, we'll be able to
// query GW just by this particular feature.
sftBuilder.setName("polygon-feature");
// Add the attributes to the feature
// Add the geometry attribute, which is mandatory for GeoWave to be able
// to construct an index out of the SimpleFeature
// Will be any arbitrary geometry; in this case, a polygon.
sftBuilder.add(attrBuilder.binding(
Geometry.class).nillable(
false).buildDescriptor(
"geometry"));
// Add another attribute just to be able to filter by it in CQL
sftBuilder.add(attrBuilder.binding(
String.class).nillable(
false).buildDescriptor(
"filter"));
// Create the SimpleFeatureType
SimpleFeatureType sfType = sftBuilder.buildFeatureType();
// We need the adapter for all our operations with GeoWave
FeatureDataAdapter sfAdapter = new FeatureDataAdapter(
sfType);
// Now we build the actual features. We'll create one polygon.
// First point
SimpleFeatureBuilder sfBuilder = new SimpleFeatureBuilder(
sfType);
// For ease of use, we'll create the polygon geometry with WKT format.
String polygonDefinition = "POLYGON (( " + "-80.3045654296875 25.852426562716428, "
+ "-80.123291015625 25.808545671771615, " + "-80.19195556640625 25.7244467526159, "
+ "-80.34233093261719 25.772068899816585, " + "-80.3045654296875 25.852426562716428" + "))";
Geometry geom = new WKTReader(
JTSFactoryFinder.getGeometryFactory()).read(polygonDefinition);
sfBuilder.set(
"geometry",
geom);
sfBuilder.set(
"filter",
"Polygon");
// When calling buildFeature, we need to pass an unique id for that
// feature, or it will be overwritten.
SimpleFeature polygon = sfBuilder.buildFeature("1");
ArrayList<SimpleFeature> features = new ArrayList<SimpleFeature>();
features.add(polygon);
// Ingest the data. For that purpose, we need the feature adapter,
// the index type (the default spatial index is used here),
// and an iterator of SimpleFeature
ingest(
sfAdapter,
new SpatialIndexBuilder().createIndex(),
features);
log.info("Polygon data ingested");
}
/**
* This query will find a polygon/polygon intersection, returning one match.
*/
private void polygonQueryCase1()
throws ParseException,
IOException {
log.info("Running Point Query Case 4");
// First, we need to obtain the adapter for the SimpleFeature we want to
// query.
// We'll query polygon-feature in this example.
// Obtain adapter for our "polygon-feature" type
ByteArrayId bfAdId = new ByteArrayId(
"polygon-feature");
FeatureDataAdapter bfAdapter = (FeatureDataAdapter) adapterStore.getAdapter(bfAdId);
// Define the geometry to query. We'll find all polygons that intersect
// with this geometry.
String queryPolygonDefinition = "POLYGON (( " + "-80.4037857055664 25.81596330265488, "
+ "-80.27915954589844 25.788144792391982, " + "-80.34370422363281 25.8814655232439, "
+ "-80.44567108154297 25.896291175546626, " + "-80.4037857055664 25.81596330265488" + "))";
Geometry queryPolygon = new WKTReader(
JTSFactoryFinder.getGeometryFactory()).read(queryPolygonDefinition);
// Perform the query.Parameters are
/**
* 1- Adapter previously obtained from the feature name. 2- Default
* spatial index. 3- A SpatialQuery, which takes the query geometry -
* aka Bounding box 4- Filters. For this example, we don't use filters
* 5- Limit. Same as standard SQL limit. 0 is no limits. 6- Accumulo
* authorizations. For our mock instances, "root" works. In a real
* Accumulo setting, whatever authorization is associated to the user in
* question.
*/
final QueryOptions options = new QueryOptions(
bfAdapter,
new SpatialIndexBuilder().createIndex(),
new String[] {
"root"
});
int count = 0;
try (final CloseableIterator<SimpleFeature> iterator = dataStore.query(
options,
new SpatialQuery(
queryPolygon))) {
while (iterator.hasNext()) {
SimpleFeature sf = iterator.next();
log.info("Obtained SimpleFeature " + sf.getName().toString() + " - " + sf.getAttribute("filter"));
count++;
System.out.println("Query match: " + sf.getID());
}
log.info("Should have obtained 1 feature. -> " + (count == 1));
}
}
}