You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/09/10 10:36:17 UTC
[sis] 02/02: Provide a common ground between `FeatureQuery` and
`CoverageQuery`.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit d65b2a0595a2db7a91c98716753ec1b01a49b9f5
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Sep 10 12:24:01 2021 +0200
Provide a common ground between `FeatureQuery` and `CoverageQuery`.
---
.../java/org/apache/sis/storage/CoverageQuery.java | 125 +++++++++++++++++++--
.../org/apache/sis/storage/CoverageSubset.java | 9 +-
.../java/org/apache/sis/storage/FeatureQuery.java | 44 ++++++++
.../main/java/org/apache/sis/storage/Query.java | 74 +++++++-----
.../org/apache/sis/storage/CoverageQueryTest.java | 38 ++++++-
.../org/apache/sis/storage/FeatureQueryTest.java | 19 +++-
6 files changed, 263 insertions(+), 46 deletions(-)
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageQuery.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageQuery.java
index c668af6..c188cf1 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageQuery.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageQuery.java
@@ -16,23 +16,40 @@
*/
package org.apache.sis.storage;
+import java.util.List;
import java.util.Arrays;
import java.util.Objects;
import java.io.Serializable;
import java.math.RoundingMode;
+import org.opengis.util.GenericName;
+import org.opengis.util.InternationalString;
+import org.opengis.geometry.Envelope;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.apache.sis.measure.Angle;
import org.apache.sis.measure.Latitude;
import org.apache.sis.measure.Longitude;
import org.apache.sis.measure.AngleFormat;
+import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridRoundingMode;
import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.Vocabulary;
/**
* Definition of filtering to apply for fetching a subset of {@link GridCoverageResource}.
* This query allows requesting a subset of the coverage domain and the range.
*
+ * <h2>Terminology</h2>
+ * This class uses relational database terminology for consistency with generic queries:
+ * <ul>
+ * <li>A <cite>selection</cite> is a filter choosing the cells or pixels to include in the subset.
+ * In this context, the selection is the <cite>coverage domain</cite>.</li>
+ * <li>A <cite>projection</cite> (not to be confused with map projection) is the set of sample values to keep.
+ * In this context, the projection is the <cite>coverage range</cite> (i.e. set of sample dimensions).</li>
+ * </ul>
+ *
* <h2>Optional values</h2>
* All aspects of this query are optional and initialized to "none".
* Unless otherwise specified, all methods accept a null argument or can return a null value, which means "none".
@@ -51,15 +68,23 @@ public class CoverageQuery extends Query implements Cloneable, Serializable {
/**
* Desired grid extent and resolution, or {@code null} for reading the whole domain.
+ * This is the "selection" in query terminology.
*/
private GridGeometry domain;
/**
* 0-based indices of sample dimensions to read, or {@code null} for reading them all.
+ * This is the "projection" (not to be confused with map projection) in query terminology.
*/
private int[] range;
/**
+ * The {@linkplain #range} specified by names instead than indices.
+ * At most one of {@code range} and {@code rangeNames} shall be non-null.
+ */
+ private String[] rangeNames;
+
+ /**
* Number of additional cells to read on each border of the source grid coverage.
* If non-zero, this property expands the {@linkplain #domain} to be read by an amount
* specified in unit of cells of the image to be read. Those cells do not necessarily
@@ -74,26 +99,67 @@ public class CoverageQuery extends Query implements Cloneable, Serializable {
}
/**
- * Sets the desired grid extent and resolution.
+ * Sets the approximate area of cells or pixels to include in the subset.
+ * This convenience method creates a grid geometry containing only the given envelope.
+ * Note that the given envelope is approximate:
+ * Coverages may expand the envelope to an integer amount of tiles.
+ *
+ * @param domain the approximate area of interest, or {@code null} if none.
+ */
+ @Override
+ public void setSelection(final Envelope domain) {
+ GridGeometry g = null;
+ if (domain != null) {
+ g = new GridGeometry(null, null, domain, GridRoundingMode.NEAREST);
+ }
+ setSelection(g);
+ }
+
+ /**
+ * Sets the desired grid extent and resolution. The given domain is approximate:
+ * Coverages may use a different resolution and expand the envelope to an integer amount of tiles.
*
* @param domain desired grid extent and resolution, or {@code null} for reading the whole domain.
*/
- public void setDomain(final GridGeometry domain) {
+ public void setSelection(final GridGeometry domain) {
this.domain = domain;
}
/**
* Returns the desired grid extent and resolution.
- * This is the value set by the last call to {@link #setDomain(GridGeometry)}.
+ * This is the value set by the last call to {@link #setSelection(GridGeometry)}.
+ *
+ * <div class="note"><b>Note on terminology:</b>
+ * "selection" is the generic term used in queries for designating a subset of feature instances.
+ * In a grid coverage, feature instances are cells or pixels.
+ * So this concept maps to the <cite>coverage domain</cite>.</div>
*
* @return desired grid extent and resolution, or {@code null} for reading the whole domain.
*/
- public GridGeometry getDomain() {
+ public GridGeometry getSelection() {
return domain;
}
/**
- * Sets the indices of samples dimensions to read.
+ * Sets the sample dimensions to read by their names.
+ *
+ * @param range sample dimensions to retrieve, or {@code null} to retrieve all properties.
+ * @throws IllegalArgumentException if a sample dimension is duplicated.
+ */
+ @Override
+ @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
+ public void setProjection(String... range) {
+ if (range != null) {
+ range = range.clone();
+ ArgumentChecks.ensureNonEmpty("range", range);
+ // Assign only after we verified that the argument is valid.
+ }
+ rangeNames = range;
+ this.range = null;
+ }
+
+ /**
+ * Sets the indices of samples dimensions to read (the <cite>coverage range</cite>).
* A {@code null} value means to read all sample dimensions (no filtering on range).
* If non-null, then the {@code range} array shall contain at least one element,
* all elements must be positive and no value can be duplicated.
@@ -102,28 +168,61 @@ public class CoverageQuery extends Query implements Cloneable, Serializable {
* @throws IllegalArgumentException if the given array is empty or contains negative or duplicated values.
*/
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
- public void setRange(int... range) {
+ public void setProjection(int... range) {
if (range != null) {
range = range.clone();
ArgumentChecks.ensureNonEmpty("range", range, 0, Integer.MAX_VALUE, true);
// Assign only after we verified that the argument is valid.
}
this.range = range;
+ rangeNames = null;
}
/**
* Returns the indices of samples dimensions to read, or {@code null} if there is no filtering on range.
* If non-null, the returned array shall never be empty.
*
+ * <div class="note"><b>Note on terminology:</b>
+ * "projection" (not to be confused with map projection) is the generic term used in queries
+ * for designating a subset of feature properties retained in each feature instances.
+ * In a coverage, this concept maps to the <cite>coverage range</cite>.</div>
+ *
* @return 0-based indices of sample dimensions to read, or {@code null} for reading them all.
*/
- public int[] getRange() {
+ public int[] getProjection() {
return (range != null) ? range.clone() : null;
}
/**
+ * Converts the sample dimension names to sample dimension indices.
+ * This conversion depends on the resource on which the query will be applied.
+ *
+ * @param source the resource for which to to the conversion.
+ */
+ private void namesToIndices(final GridCoverageResource source) throws DataStoreException {
+ if (rangeNames != null) {
+ final List<SampleDimension> sd = source.getSampleDimensions();
+ final int numBands = sd.size();
+ range = new int[rangeNames.length];
+next: for (int i=0; i<rangeNames.length; i++) {
+ final String name = rangeNames[i];
+ for (int j=0; j<numBands; j++) {
+ if (name.equals(sd.get(j).getName().toString())) {
+ range[i] = j;
+ continue next;
+ }
+ }
+ InternationalString id = source.getIdentifier().map(GenericName::toInternationalString)
+ .orElseGet(() -> Vocabulary.formatInternational(Vocabulary.Keys.Unnamed));
+ throw new UnsupportedQueryException(Errors.format(Errors.Keys.PropertyNotFound_2, id, name));
+ }
+ rangeNames = null;
+ }
+ }
+
+ /**
* Sets a number of additional cells to read on each border of the source grid coverage.
- * If non-zero, this property expands the {@linkplain #getDomain() domain} to be read
+ * If non-zero, this property expands the {@link #getSelection() domain} to be read
* by the specified margin.
*
* <h4>Unit of measurement</h4>
@@ -133,7 +232,7 @@ public class CoverageQuery extends Query implements Cloneable, Serializable {
* the full image to be read from the resource. Cells are counted after subsampling,
* e.g. cells are twice bigger if a subsampling of 2 is applied.
* Those cells do not necessarily have the same size than the cells
- * of the {@linkplain #getDomain() domain of this query}.
+ * of the {@link #getSelection() domain of this query}.
*
* <h4>Use case</h4>
* At reading time it may be necessary to add a margin to the coverage extent.
@@ -163,11 +262,13 @@ public class CoverageQuery extends Query implements Cloneable, Serializable {
*
* @param source the coverage resource to filter.
* @return a view over the given coverage resource containing only the given domain and range.
- * @throws UnsupportedQueryException if this query contains filtering options not yet supported.
+ * @throws DataStoreException if an error occurred during creation of the subset.
*/
- final GridCoverageResource execute(final GridCoverageResource source) throws UnsupportedQueryException {
+ final GridCoverageResource execute(final GridCoverageResource source) throws DataStoreException {
ArgumentChecks.ensureNonNull("source", source);
- return new CoverageSubset(source, clone());
+ final CoverageQuery query = clone();
+ query.namesToIndices(source);
+ return new CoverageSubset(source, query);
}
/**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageSubset.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageSubset.java
index 4ff419f..b93ef03 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageSubset.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/CoverageSubset.java
@@ -54,6 +54,7 @@ final class CoverageSubset extends AbstractGridResource {
/**
* Creates a new coverage resource by filtering the given coverage using the given query.
+ * This given query is stored as-is (it is not cloned neither optimized).
*
* @param source the coverage resource instances which provides the data.
* @param query the domain and range to read from the {@code source} coverage.
@@ -99,7 +100,7 @@ final class CoverageSubset extends AbstractGridResource {
private GridGeometry clip(final GridGeometry domain, final GridRoundingMode rounding, final GridClippingMode clipping)
throws DataStoreException
{
- final GridGeometry areaOfInterest = query.getDomain();
+ final GridGeometry areaOfInterest = query.getSelection();
if (domain == null) return areaOfInterest;
if (areaOfInterest == null) return domain;
try {
@@ -135,7 +136,7 @@ final class CoverageSubset extends AbstractGridResource {
@Override
public List<SampleDimension> getSampleDimensions() throws DataStoreException {
final List<SampleDimension> dimensions = source.getSampleDimensions();
- final int[] range = query.getRange();
+ final int[] range = query.getProjection();
if (range == null) {
return dimensions;
}
@@ -155,7 +156,7 @@ final class CoverageSubset extends AbstractGridResource {
* Loads a subset of the grid coverage represented by this resource.
* The domain to be read by the resource is computed as below:
* <ul>
- * <li>If the query specifies a {@linkplain CoverageQuery#getDomain() domain},
+ * <li>If the query specifies a {@link CoverageQuery#getSelection() domain},
* the given domain is intersected with the query domain.</li>
* <li>If the query specifies a {@linkplain CoverageQuery#getSourceDomainExpansion() domain expansion},
* the given domain is expanded by the amount specified in the query.</li>
@@ -176,7 +177,7 @@ final class CoverageSubset extends AbstractGridResource {
* specified `domain` but inside the source domain, which may be larger.
*/
domain = clip(domain, GridRoundingMode.ENCLOSING, GridClippingMode.BORDER_EXPANSION);
- final int[] qr = query.getRange();
+ final int[] qr = query.getProjection();
if (range == null) {
range = qr;
} else if (qr != null) {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
index 927ec64..c2b4e6c 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
@@ -25,11 +25,14 @@ import java.io.Serializable;
import javax.measure.Quantity;
import javax.measure.quantity.Length;
import org.opengis.util.GenericName;
+import org.opengis.geometry.Envelope;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.feature.builder.PropertyTypeBuilder;
+import org.apache.sis.internal.feature.AttributeConvention;
import org.apache.sis.internal.feature.FeatureExpression;
import org.apache.sis.internal.filter.SortByComparator;
import org.apache.sis.internal.storage.Resources;
+import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.filter.Optimization;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Classes;
@@ -39,6 +42,7 @@ import org.apache.sis.util.iso.Names;
// Branch-dependent imports
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
+import org.opengis.filter.FilterFactory;
import org.opengis.filter.Filter;
import org.opengis.filter.Expression;
import org.opengis.filter.Literal;
@@ -150,6 +154,29 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
}
/**
+ * Sets the properties to retrieve by their names. This convenience method wraps the
+ * given names in {@link ValueReference} expressions without alias and delegates to
+ * {@link #setProjection(NamedExpression...)}.
+ *
+ * @param properties properties to retrieve, or {@code null} to retrieve all properties.
+ * @throws IllegalArgumentException if a property is duplicated.
+ */
+ @Override
+ public void setProjection(final String... properties) {
+ NamedExpression[] wrappers = null;
+ if (properties != null) {
+ final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
+ wrappers = new NamedExpression[properties.length];
+ for (int i=0; i<wrappers.length; i++) {
+ final String p = properties[i];
+ ArgumentChecks.ensureNonNullElement("properties", i, p);
+ wrappers[i] = new NamedExpression(ff.property(p));
+ }
+ }
+ setProjection(wrappers);
+ }
+
+ /**
* Sets the properties to retrieve, or {@code null} if all properties shall be included in the query.
* This convenience method wraps the given expression in {@link NamedExpression}s without alias and
* delegates to {@link #setProjection(NamedExpression...)}.
@@ -213,6 +240,23 @@ public class FeatureQuery extends Query implements Cloneable, Serializable {
}
/**
+ * Sets the approximate area of feature instances to include in the subset.
+ * This convenience method creates a filter that checks if the bounding box
+ * of the feature's {@code "sis:geometry"} property interacts with the given envelope.
+ *
+ * @param domain the approximate area of interest, or {@code null} if none.
+ */
+ @Override
+ public void setSelection(final Envelope domain) {
+ Filter<? super Feature> filter = null;
+ if (domain != null) {
+ final FilterFactory<Feature,Object,?> ff = DefaultFilterFactory.forFeatures();
+ filter = ff.bbox(ff.property(AttributeConvention.GEOMETRY), domain);
+ }
+ setSelection(filter);
+ }
+
+ /**
* Sets a filter for trimming feature instances.
* Features that do not pass the filter are discarded.
* Discarded features are not counted for the {@linkplain #setLimit(long) query limit}.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
index 2aa4b8f..4d58d2f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
@@ -16,39 +16,36 @@
*/
package org.apache.sis.storage;
-import org.opengis.feature.Feature;
+import org.opengis.geometry.Envelope;
+
+// Branch-dependent imports
import org.opengis.filter.QueryExpression;
/**
* Definition of filtering to apply for fetching a resource subset.
* Filtering can be applied on {@link FeatureSet} or on {@link GridCoverageResource}.
- * When applied on {@link FeatureSet}, filtering can happen in two domains:
- *
- * <ol>
- * <li>By filtering the {@link Feature} instances.</li>
- * <li>By filtering the {@linkplain org.apache.sis.feature.DefaultFeatureType#getProperty properties}
- * in each feature instance.</li>
- * </ol>
- *
- * Compared to Java functional interfaces, the first domain is equivalent to using
- * <code>{@linkplain java.util.function.Predicate}<{@linkplain Feature}></code>
- * while the second domain is equivalent to using
- * <code>{@linkplain java.util.function.UnaryOperator}<{@linkplain Feature}></code>.
+ * A query contains at least two parts:
*
- * <div class="note"><b>Note:</b>
- * it is technically possible to use {@code Query} for performing more generic feature transformations,
- * for example inserting new properties computed from other properties, but such {@code Query} usages
- * should be rare since transformations (or more generic processing) are the topic of another package.
- * Queries are rather descriptive objects used by {@link FeatureSet} to optimize search operations
- * as much as possible on the resource, using for example caches and indexes.</div>
+ * <ul>
+ * <li><b>Selection</b> for choosing the feature instances to fetch.
+ * This is equivalent to choosing rows in a database table.</li>
+ * <li><b>Projection</b> (not to be confused with map projection) for choosing the
+ * {@linkplain org.apache.sis.feature.DefaultFeatureType#getProperty feature properties} or the
+ * {@linkplain org.apache.sis.coverage.SampleDimension coverage sample dimensions} to fetch.
+ * This is equivalent to choosing columns in a database table.</li>
+ * </ul>
*
* Compared to the SQL language, {@code Query} contains the information in the {@code SELECT} and
* {@code WHERE} clauses of a SQL statement. A {@code Query} typically contains filtering capabilities
* and (sometime) simple attribute transformations. Well known query languages include SQL and CQL.
*
+ * <h2>Optional values</h2>
+ * All aspects of this query are optional and initialized to "none".
+ * Unless otherwise specified, all methods accept a null argument or can return a null value, which means "none".
+ *
* @author Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
*
* @see FeatureSet#subset(Query)
* @see GridCoverageResource#subset(Query)
@@ -57,15 +54,40 @@ import org.opengis.filter.QueryExpression;
* @module
*/
public abstract class Query implements QueryExpression {
- /*
- * Current version does not yet contain any field. But some fields may be added in the future.
- * For example some methods from org.apache.sis.internal.storage.query.FeatureQuery may move here.
- * We use an abstract class instead than an interface for that reason.
- */
-
/**
* Creates a new, initially empty, query.
*/
protected Query() {
}
+
+ /**
+ * Sets the approximate area of feature instances or pixels to include in the subset.
+ * For feature set, the domain is materialized by a {@link org.opengis.filter.Filter}.
+ * For grid coverage resource, the given envelope specifies the coverage domain.
+ *
+ * <p>The given envelope is approximate.
+ * Features may test intersections using only bounding boxes instead of full geometries.
+ * Coverages may expand the envelope to an integer amount of tiles.</p>
+ *
+ * @param domain the approximate area of interest, or {@code null} if none.
+ *
+ * @since 1.1
+ */
+ public abstract void setSelection(Envelope domain);
+
+ /**
+ * Sets the properties to retrieve by their names. For features, the arguments are names of
+ * {@linkplain org.apache.sis.feature.DefaultFeatureType#getProperty feature properties}.
+ * For coverages, the arguments are names of
+ * {@linkplain org.apache.sis.coverage.BandedCoverage#getSampleDimensions() sample dimensions}.
+ *
+ * <p><b>Note:</b> in this context, the "projection" word come from relational database terminology.
+ * It is unrelated to <cite>map projection</cite>.</p>
+ *
+ * @param properties properties to retrieve, or {@code null} to retrieve all properties.
+ * @throws IllegalArgumentException if a property is duplicated.
+ *
+ * @since 1.1
+ */
+ public abstract void setProjection(String... properties);
}
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/CoverageQueryTest.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/CoverageQueryTest.java
index eef4d96..305908c 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/CoverageQueryTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/CoverageQueryTest.java
@@ -99,7 +99,7 @@ public final strictfp class CoverageQueryTest extends TestCase {
public void testWithSubgrid() throws DataStoreException {
final GridGeometry subGrid = createSubGrid(0);
final CoverageQuery query = new CoverageQuery();
- query.setDomain(subGrid);
+ query.setSelection(subGrid);
final GridCoverageResource subset = resource.subset(query);
assertEquals(subGrid, subset.getGridGeometry());
@@ -116,7 +116,7 @@ public final strictfp class CoverageQueryTest extends TestCase {
final int expansion = 3;
final GridGeometry subGrid = createSubGrid(0);
final CoverageQuery query = new CoverageQuery();
- query.setDomain(subGrid);
+ query.setSelection(subGrid);
query.setSourceDomainExpansion(expansion);
final GridCoverageResource subset = resource.subset(query);
@@ -125,6 +125,40 @@ public final strictfp class CoverageQueryTest extends TestCase {
}
/**
+ * Tests using only the methods defined in the {@link Query} base class.
+ *
+ * @throws DataStoreException if query execution failed.
+ */
+ @Test
+ public void testQueryMethods() throws DataStoreException {
+ final GridGeometry subGrid = createSubGrid(0);
+ final Query query = new CoverageQuery();
+ query.setSelection(subGrid.getEnvelope());
+ query.setProjection("0");
+
+ final GridCoverageResource subset = resource.subset(query);
+ assertEquals(subGrid, subset.getGridGeometry());
+ verifyRead(subset, 0);
+ }
+
+ /**
+ * Tests using an invalid sample dimension name.
+ *
+ * @throws DataStoreException if query execution failed.
+ */
+ @Test
+ public void testInvalidName() throws DataStoreException {
+ final Query query = new CoverageQuery();
+ query.setProjection("Apple");
+ try {
+ resource.subset(query);
+ fail("Expected UnsupportedQueryException.");
+ } catch (UnsupportedQueryException e) {
+ assertTrue(e.getMessage().contains("Apple"));
+ }
+ }
+
+ /**
* Verifies that the read operation adds the expected margins.
*/
private void verifyRead(final GridCoverageResource subset, final int expansion) throws DataStoreException {
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
index cbe7414..6918ac9 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
@@ -152,7 +152,7 @@ public final strictfp class FeatureQueryTest extends TestCase {
* @throws DataStoreException if an error occurred while executing the query.
*/
@Test
- public void testFilter() throws DataStoreException {
+ public void testSelection() throws DataStoreException {
final FilterFactory<Feature,?,?> factory = DefaultFilterFactory.forFeatures();
query.setSelection(factory.equal(factory.property("value1", Integer.class),
factory.literal(2), true, MatchAction.ALL));
@@ -165,7 +165,7 @@ public final strictfp class FeatureQueryTest extends TestCase {
* @throws DataStoreException if an error occurred while executing the query.
*/
@Test
- public void testColumns() throws DataStoreException {
+ public void testProjection() throws DataStoreException {
final FilterFactory<Feature,?,?> factory = DefaultFilterFactory.forFeatures();
query.setProjection(new FeatureQuery.NamedExpression(factory.property("value1", Integer.class), (String) null),
new FeatureQuery.NamedExpression(factory.property("value1", Integer.class), "renamed1"),
@@ -194,4 +194,19 @@ public final strictfp class FeatureQueryTest extends TestCase {
assertEquals(3, result.getPropertyValue("renamed1"));
assertEquals("a literal", result.getPropertyValue("computed"));
}
+
+ /**
+ * Verifies the effect of {@link FeatureQuery#setProjection(String[])}.
+ *
+ * @throws DataStoreException if an error occurred while executing the query.
+ */
+ @Test
+ public void testProjectionByNames() throws DataStoreException {
+ query.setProjection("value2");
+ query.setLimit(1);
+ final FeatureSet fs = query.execute(featureSet);
+ final Feature result = TestUtilities.getSingleton(fs.features(false).collect(Collectors.toList()));
+ final PropertyType p = TestUtilities.getSingleton(result.getType().getProperties(true));
+ assertEquals("value2", p.getName().toString());
+ }
}