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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 11b9c21066 Bug fix: when `FeatureQuery` contains a `projection` item
which is an operation, and when one of the operation parameters is another
operation (typically a link), the operation was not executed.
11b9c21066 is described below
commit 11b9c21066c2110463f6a68d16bac677ac88d822
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Sep 15 15:07:53 2025 +0200
Bug fix: when `FeatureQuery` contains a `projection` item which is an
operation,
and when one of the operation parameters is another operation (typically a
link),
the operation was not executed.
---
.../sis/feature/builder/OperationWrapper.java | 2 +-
.../feature/privy/FeatureProjectionBuilder.java | 33 ++++++++-
.../org/apache/sis/filter/ConvertFunction.java | 2 +-
.../sis/filter/internal/GeometryFromFeature.java | 2 +-
.../main/org/apache/sis/filter/internal/Node.java | 8 +++
.../apache/sis/filter/sqlmm/SpatialFunction.java | 6 +-
.../sis/storage/sql/postgis/PostgresTest.java | 83 +++++++++++++++++++++-
7 files changed, 126 insertions(+), 10 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java
index 7a6a656f7e..9ca57b576a 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/OperationWrapper.java
@@ -28,7 +28,7 @@ import org.opengis.feature.PropertyType;
/**
* Wraps an existing operation. This package cannot create new operations,
except for a few special cases.
- * The user need to specify fully formed objects.
+ * The user needs to specify fully formed objects.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
index 51ab93d85f..49858e1f63 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
@@ -142,6 +142,14 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
*/
private final Map<String, List<Item>> dependencies;
+ /**
+ * Whether to store operations as attributes. By default, when a
{@linkplain #addSourceProperty source
+ * property is added in the projection}, operation are forwarded as given
(with their dependencies).
+ * But if this flag is set to {@code true}, then operations are replaced
by an attribute.
+ * Then, it will be caller's responsibility to store the value.
+ */
+ private boolean operationResultAsAttribute;
+
/**
* Creates a new builder instance using the default factories.
*
@@ -177,7 +185,7 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
*
* @param childType the feature type to use.
* @param expression the expression from which to get the expected type.
- * @return the expected type, or {@code null}.
+ * @return handler for the property, or {@code null} if it cannot be
resolved.
*
* @see FeatureExpression#expectedType(FeatureProjectionBuilder)
*/
@@ -194,6 +202,27 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
}
}
+ /**
+ * Adds a property from the source feature type, but replacing operation
results by attributes.
+ * This method is invoked when an operation uses a source property as a
template, usually because
+ * the result will be of the same type as one of the operation argument
(usually the first argument).
+ * In such case, the caller does not want the operation to be executed,
since the property will rather
+ * be used as a slot for receiving the result.
+ *
+ * @param childType the feature type to use.
+ * @param expression the expression from which to get the expected type.
+ * @return handler for the property, or {@code null} if it cannot be
resolved.
+ */
+ public Item addTemplateProperty(final FeatureExpression<?,?> expression) {
+ final boolean status = operationResultAsAttribute;
+ try {
+ operationResultAsAttribute = true;
+ return expression.expectedType(this);
+ } finally {
+ operationResultAsAttribute = status;
+ }
+ }
+
/**
* Adds the given property, replacing operation by an attribute storing
the operation result.
* This method may return {@code null} if it cannot resolve the property
type, in which case
@@ -259,7 +288,7 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
reserve(property.getName(), null);
deferred = new ArrayList<>();
builder = addPropertyResult(property, deferred);
- } else if (property instanceof AbstractOperation) {
+ } else if (!operationResultAsAttribute && property instanceof
AbstractOperation) {
/*
* For operations, remember the dependencies in order to determine
(after we added all properties)
* if we can keep the property as an operation or if we will need
to copy the value in an attribute.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
index c614215ed9..cc00434d9d 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ConvertFunction.java
@@ -174,7 +174,7 @@ final class ConvertFunction<R,S,V> extends
UnaryFunction<R,S>
if (fex == null) {
return null;
}
- final FeatureProjectionBuilder.Item item = fex.expectedType(addTo);
+ final FeatureProjectionBuilder.Item item =
addTo.addTemplateProperty(fex);
item.replaceValueClass((c) -> getValueClass());
return item;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java
index 178a668113..07244202f4 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/GeometryFromFeature.java
@@ -97,7 +97,7 @@ final class GeometryFromFeature<G> extends
GeometryConverter<Feature, G> {
@Override
public GeometryWrapper apply(final Feature input) {
final GeometryWrapper wrapper = super.apply(input);
- if (wrapper.getCoordinateReferenceSystem() == null) {
+ if (wrapper != null && wrapper.getCoordinateReferenceSystem() == null)
{
final CoordinateReferenceSystem crs =
AttributeConvention.getCRSCharacteristic(input, propertyName);
if (crs != null) {
wrapper.setCoordinateReferenceSystem(crs);
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
index 5be96dd66d..42d48014a7 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
@@ -28,6 +28,7 @@ import java.io.Serializable;
import org.opengis.util.CodeList;
import org.opengis.util.LocalName;
import org.opengis.util.ScopedName;
+import org.opengis.referencing.IdentifiedObject;
import org.apache.sis.math.FunctionProperty;
import org.apache.sis.feature.DefaultAttributeType;
import org.apache.sis.feature.internal.Resources;
@@ -35,6 +36,9 @@ import org.apache.sis.feature.privy.FeatureExpression;
import org.apache.sis.filter.privy.WarningEvent;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.geometry.wrapper.GeometryWrapper;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.privy.ReferencingUtilities;
+import org.apache.sis.util.Classes;
import org.apache.sis.util.iso.Names;
import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.util.collection.DefaultTreeTable;
@@ -328,6 +332,10 @@ public abstract class Node implements Serializable {
} else {
value = Vocabulary.format(Vocabulary.Keys.CycleOmitted);
}
+ } else if (child instanceof IdentifiedObject) {
+ final var object = (IdentifiedObject) child;
+ value =
Classes.getShortName(ReferencingUtilities.getInterface(object))
+ + "[“" + IdentifiedObjects.getDisplayName(object,
null) + "”]";
} else {
value = String.valueOf(child);
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
index b938087a8a..8d24f49ba2 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
@@ -174,8 +174,8 @@ abstract class SpatialFunction<R> extends Node implements
FeatureExpression<R,Ob
* <ul class="verbose">
* <li>If the operation expects at least one geometric parameter and
returns a geometry,
* then the characteristics of the first parameter (in particular
the CRS) are copied.
- * The first parameter is used as a template for compliance with
SQLMM specification.</li>
- * <li>Otherwise an attribute is created with the return value specified
by the operation.</li>
+ * The use of the first parameter is mandated by the
<abbr>SQLMM</abbr> specification.</li>
+ * <li>Otherwise, an attribute is created with the return value
specified by the operation.</li>
* </ul>
*
* @param addTo where to add the type of properties evaluated by this
expression.
@@ -189,7 +189,7 @@ abstract class SpatialFunction<R> extends Node implements
FeatureExpression<R,Ob
if (operation.isGeometryInOut()) {
final FeatureExpression<?,?> fex =
FeatureExpression.castOrCopy(getParameters().get(0));
if (fex != null) {
- final FeatureProjectionBuilder.Item item =
fex.expectedType(addTo);
+ final FeatureProjectionBuilder.Item item =
addTo.addTemplateProperty(fex);
final boolean success = item.replaceValueClass((c) -> {
final Geometries<?> library = Geometries.factory(c);
return (library == null) ? null :
operation.getReturnType(library);
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
index 0ee3a909d0..927b7fe9d4 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java
@@ -42,16 +42,18 @@ import
org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.setup.OptionKey;
import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.storage.FeatureQuery;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.sql.SQLStore;
import org.apache.sis.storage.sql.SQLStoreProvider;
import org.apache.sis.storage.sql.SimpleFeatureStore;
import org.apache.sis.storage.sql.ResourceDefinition;
+import org.apache.sis.storage.sql.feature.BinaryEncoding;
import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.feature.privy.AttributeConvention;
import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.io.stream.ChannelDataInput;
-import org.apache.sis.storage.sql.feature.BinaryEncoding;
import org.apache.sis.geometry.wrapper.jts.JTS;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.util.Version;
@@ -158,6 +160,7 @@ public final class PostgresTest extends TestCase {
testAllFeatures(resource);
testFilteredFeatures(resource, false);
testFilteredFeatures(resource, true);
+ testGeometryTransform(resource);
}
}
}
@@ -263,6 +266,61 @@ public final class PostgresTest extends TestCase {
}
}
+ /**
+ * Tests the {@code ST_Transform} <abbr>SQLMM</abbr> operation.
+ *
+ * @param resource the set of all features.
+ */
+ private static void testGeometryTransform(final FeatureSet resource)
throws Exception {
+ final var factory = DefaultFilterFactory.forFeatures();
+ final var targetCRS =
factory.literal(GeometryGetterTest.getExpectedCRS(4326));
+ final var geometry = factory.function("ST_Transform",
factory.property("geometry"), targetCRS);
+ final var alias = factory.function("ST_Transform",
factory.property(AttributeConvention.GEOMETRY), targetCRS);
+ final var query = new FeatureQuery();
+ query.setProjection(new
FeatureQuery.NamedExpression(factory.property("filename")),
+ new FeatureQuery.NamedExpression(geometry,
"transformed"),
+ new FeatureQuery.NamedExpression(alias),
+ new
FeatureQuery.NamedExpression(factory.property("image")));
+ final FeatureSet subset = resource.subset(query);
+ assertNull(getCRSCharacteristic(resource, "geometry"), "Expected no
CRS because it is not the same for all rows.");
+ assertNull(getCRSCharacteristic(resource,
AttributeConvention.GEOMETRY));
+ assertEquals(targetCRS.getValue(), getCRSCharacteristic(subset,
"transformed"));
+ assertEquals(targetCRS.getValue(), getCRSCharacteristic(subset,
AttributeConvention.GEOMETRY));
+ subset.features(false).forEach(PostgresTest::validateTransformed);
+ }
+
+ /**
+ * Invoked for each feature instances which is expected to have been
transformed to WGS84.
+ */
+ private static void validateTransformed(final Feature feature) {
+ final Geometry geometry;
+ switch (feature.getPropertyValue("filename").toString()) {
+ case "point-prj": {
+ final var p = (Point) feature.getPropertyValue("transformed");
+ assertEquals(1.79663056824E-5, p.getX(), 1E-14);
+ assertEquals(2.71310843105E-5, p.getY(), 1E-14);
+ geometry = p;
+ break;
+ }
+ case "polygon-prj": {
+ geometry = (Geometry) feature.getPropertyValue("transformed");
+ final var envelope = geometry.getEnvelopeInternal();
+ // Tolerance is specified even if zero in order to ignore the
sign of zero.
+ assertEquals(0, envelope.getMinX(), 0);
+ assertEquals(0, envelope.getMinY(), 0);
+ assertEquals(8.983152841195214E-6, envelope.getMaxX(), 1E-14);
+ assertEquals(9.043694770179478E-6, envelope.getMaxY(), 1E-14);
+ break;
+ }
+ default: {
+ validate(feature);
+ return;
+ }
+ }
+ verifySRID(4326, geometry);
+ assertEquals(geometry,
feature.getPropertyValue(AttributeConvention.GEOMETRY));
+ }
+
/**
* Invoked for each feature instances for performing some checks on the
feature.
* This method performs only a superficial verification of geometries.
@@ -300,6 +358,18 @@ public final class PostgresTest extends TestCase {
case "multi-polygon": geomSRID = 4326; break;
default: throw new AssertionError(filename);
}
+ assertNull(raster);
+ verifySRID(geomSRID, geometry);
+ assertEquals(geometry,
feature.getPropertyValue(AttributeConvention.GEOMETRY));
+ }
+
+ /**
+ * Asserts that the given geometry as the expected <abbr>CRS</abbr>.
+ *
+ * @param geomSRID the expected reference system identifier.
+ * @param geometry the geometry to validate.
+ */
+ private static void verifySRID(final int geomSRID, final Geometry
geometry) {
try {
final CoordinateReferenceSystem expected =
GeometryGetterTest.getExpectedCRS(geomSRID);
final CoordinateReferenceSystem actual =
JTS.getCoordinateReferenceSystem(geometry);
@@ -314,6 +384,15 @@ public final class PostgresTest extends TestCase {
} catch (FactoryException e) {
throw new AssertionError(e);
}
- assertNull(raster);
+ }
+
+ /**
+ * Returns the {@code crs} characteristic of the geometry column of the
given resource.
+ */
+ private static CoordinateReferenceSystem getCRSCharacteristic(final
FeatureSet resource, final String property)
+ throws DataStoreException
+ {
+ final var type = resource.getType();
+ return AttributeConvention.getCRSCharacteristic(type,
type.getProperty(property));
}
}