package mil.nga.giat.geowave.test.query; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.geotools.data.DataUtilities; import org.geotools.feature.SchemaException; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import mil.nga.giat.geowave.adapter.vector.FeatureDataAdapter; import mil.nga.giat.geowave.adapter.vector.util.FeatureTranslatingIterator; import mil.nga.giat.geowave.core.geotime.GeometryUtils; import mil.nga.giat.geowave.core.geotime.store.query.SpatialQuery; import mil.nga.giat.geowave.core.store.CloseableIterator; import mil.nga.giat.geowave.core.store.IndexWriter; import mil.nga.giat.geowave.core.store.operations.remote.options.DataStorePluginOptions; import mil.nga.giat.geowave.core.store.query.Query; import mil.nga.giat.geowave.core.store.query.QueryOptions; import mil.nga.giat.geowave.test.GeoWaveITRunner; import mil.nga.giat.geowave.test.TestUtils; import mil.nga.giat.geowave.test.annotation.GeoWaveTestStore; import mil.nga.giat.geowave.test.annotation.GeoWaveTestStore.GeoWaveStoreType; @RunWith(GeoWaveITRunner.class) public class AttributesSubsetQueryIT { private static final Logger LOGGER = LoggerFactory.getLogger(AttributesSubsetQueryIT.class); private static SimpleFeatureType simpleFeatureType; private static FeatureDataAdapter dataAdapter; @GeoWaveTestStore({ GeoWaveStoreType.ACCUMULO, GeoWaveStoreType.BIGTABLE, GeoWaveStoreType.HBASE }) protected DataStorePluginOptions dataStore; // constants for attributes of SimpleFeatureType private static final String CITY_ATTRIBUTE = "city"; private static final String STATE_ATTRIBUTE = "state"; private static final String POPULATION_ATTRIBUTE = "population"; private static final String LAND_AREA_ATTRIBUTE = "landArea"; private static final String GEOMETRY_ATTRIBUTE = "geometry"; private static final Collection<String> ALL_ATTRIBUTES = Arrays.asList( CITY_ATTRIBUTE, STATE_ATTRIBUTE, POPULATION_ATTRIBUTE, LAND_AREA_ATTRIBUTE, GEOMETRY_ATTRIBUTE); // points used to construct bounding box for queries private static final Coordinate GUADALAJARA = new Coordinate( -103.3500, 20.6667); private static final Coordinate ATLANTA = new Coordinate( -84.3900, 33.7550); private final Query spatialQuery = new SpatialQuery( GeometryUtils.GEOMETRY_FACTORY.toGeometry(new Envelope( GUADALAJARA, ATLANTA))); private static long startMillis; @BeforeClass public static void setupData() throws IOException { simpleFeatureType = getSimpleFeatureType(); dataAdapter = new FeatureDataAdapter( simpleFeatureType); startMillis = System.currentTimeMillis(); LOGGER.warn("-----------------------------------------"); LOGGER.warn("* *"); LOGGER.warn("* RUNNING AttributesSubsetQueryIT *"); LOGGER.warn("* *"); LOGGER.warn("-----------------------------------------"); } @AfterClass public static void reportTest() { LOGGER.warn("-----------------------------------------"); LOGGER.warn("* *"); LOGGER.warn("* FINISHED AttributesSubsetQueryIT *"); LOGGER .warn("* " + ((System.currentTimeMillis() - startMillis) / 1000) + "s elapsed. *"); LOGGER.warn("* *"); LOGGER.warn("-----------------------------------------"); } @Test public void testNoFiltering() throws IOException { final CloseableIterator<SimpleFeature> results = dataStore.createDataStore().query( new QueryOptions( dataAdapter, TestUtils.DEFAULT_SPATIAL_INDEX), spatialQuery); // query expects to match 3 cities from Texas, which should each contain // non-null values for each SimpleFeature attribute verifyResults( results, 3, ALL_ATTRIBUTES); } @Test public void testServerSideFiltering() throws IOException { final QueryOptions queryOptions = new QueryOptions( dataAdapter, TestUtils.DEFAULT_SPATIAL_INDEX); queryOptions.setFieldIds( Arrays.asList(CITY_ATTRIBUTE), dataAdapter); CloseableIterator<SimpleFeature> results = dataStore.createDataStore().query( queryOptions, spatialQuery); // query expects to match 3 cities from Texas, which should each contain // non-null values for a subset of attributes (city) and nulls for the // rest List<String> expectedAttributes = Arrays.asList( CITY_ATTRIBUTE, GEOMETRY_ATTRIBUTE); // always included verifyResults( results, 3, expectedAttributes); queryOptions.setFieldIds( Arrays.asList(GEOMETRY_ATTRIBUTE), dataAdapter); // now try just geometry results = dataStore.createDataStore().query( queryOptions, spatialQuery); // query expects to match 3 cities from Texas, which should each contain // non-null values for geometry and null values for all other attributes expectedAttributes = Arrays.asList(GEOMETRY_ATTRIBUTE); // always // included verifyResults( results, 3, expectedAttributes); } @Test public void testClientSideFiltering() throws IOException { final List<String> attributesSubset = Arrays.asList( CITY_ATTRIBUTE, POPULATION_ATTRIBUTE); final CloseableIterator<SimpleFeature> results = dataStore.createDataStore().query( new QueryOptions( dataAdapter, TestUtils.DEFAULT_SPATIAL_INDEX), spatialQuery); // query expects to match 3 cities from Texas, which should each contain // non-null values for a subset of attributes (city, population) and // nulls for the rest verifyResults( // performs filtering client side new FeatureTranslatingIterator( simpleFeatureType, attributesSubset, results), 3, attributesSubset); } private void verifyResults( final CloseableIterator<SimpleFeature> results, final int numExpectedResults, final Collection<String> attributesExpected ) throws IOException { int numResults = 0; SimpleFeature currentFeature; Object currentAttributeValue; while (results.hasNext()) { currentFeature = results.next(); numResults++; for (final String currentAttribute : ALL_ATTRIBUTES) { currentAttributeValue = currentFeature.getAttribute(currentAttribute); if (attributesExpected.contains(currentAttribute)) { Assert.assertNotNull( "Expected non-null " + currentAttribute + " value!", currentAttributeValue); } else { Assert.assertNull( "Expected null " + currentAttribute + " value!", currentAttributeValue); } } } results.close(); Assert.assertEquals( "Unexpected number of query results", numExpectedResults, numResults); } private static SimpleFeatureType getSimpleFeatureType() { SimpleFeatureType type = null; try { type = DataUtilities.createType( "testCityData", CITY_ATTRIBUTE + ":String," + STATE_ATTRIBUTE + ":String," + POPULATION_ATTRIBUTE + ":Double," + LAND_AREA_ATTRIBUTE + ":Double," + GEOMETRY_ATTRIBUTE + ":Geometry"); } catch (final SchemaException e) { LOGGER.error( "Unable to create SimpleFeatureType", e); } return type; } @Before public void ingestSampleData() throws IOException { LOGGER.info("Ingesting canned data..."); try (IndexWriter writer = dataStore.createDataStore().createWriter( dataAdapter, TestUtils.DEFAULT_SPATIAL_INDEX)) { for (final SimpleFeature sf : buildCityDataSet()) { writer.write(sf); } } LOGGER.info("Ingest complete."); } @After public void deleteSampleData() throws IOException { LOGGER.info("Deleting canned data..."); TestUtils.deleteAll(dataStore); LOGGER.info("Delete complete."); } private static List<SimpleFeature> buildCityDataSet() { final List<SimpleFeature> points = new ArrayList<>(); // http://en.wikipedia.org/wiki/List_of_United_States_cities_by_population points.add(buildSimpleFeature( "New York", "New York", 8405837, 302.6, new Coordinate( -73.9385, 40.6643))); points.add(buildSimpleFeature( "Los Angeles", "California", 3884307, 468.7, new Coordinate( -118.4108, 34.0194))); points.add(buildSimpleFeature( "Chicago", "Illinois", 2718782, 227.6, new Coordinate( -87.6818, 41.8376))); points.add(buildSimpleFeature( "Houston", "Texas", 2195914, 599.6, new Coordinate( -95.3863, 29.7805))); points.add(buildSimpleFeature( "Philadelphia", "Pennsylvania", 1553165, 134.1, new Coordinate( -75.1333, 40.0094))); points.add(buildSimpleFeature( "Phoenix", "Arizona", 1513367, 516.7, new Coordinate( -112.088, 33.5722))); points.add(buildSimpleFeature( "San Antonio", "Texas", 1409019, 460.9, new Coordinate( -98.5251, 29.4724))); points.add(buildSimpleFeature( "San Diego", "California", 1355896, 325.2, new Coordinate( -117.135, 32.8153))); points.add(buildSimpleFeature( "Dallas", "Texas", 1257676, 340.5, new Coordinate( -96.7967, 32.7757))); points.add(buildSimpleFeature( "San Jose", "California", 998537, 176.5, new Coordinate( -121.8193, 37.2969))); return points; } private static SimpleFeature buildSimpleFeature( final String city, final String state, final double population, final double landArea, final Coordinate coordinate ) { final SimpleFeatureBuilder builder = new SimpleFeatureBuilder( simpleFeatureType); builder.set( CITY_ATTRIBUTE, city); builder.set( STATE_ATTRIBUTE, state); builder.set( POPULATION_ATTRIBUTE, population); builder.set( LAND_AREA_ATTRIBUTE, landArea); builder.set( GEOMETRY_ATTRIBUTE, GeometryUtils.GEOMETRY_FACTORY.createPoint(coordinate)); return builder.buildFeature(UUID.randomUUID().toString()); } }