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 83e7a4c6e7 Fix: ST_Transform does nothing when the CRS is declared on
the attribute type. https://issues.apache.org/jira/browse/SIS-621
83e7a4c6e7 is described below
commit 83e7a4c6e72e2c9686d4389d2581e198e32570d6
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Oct 28 22:41:01 2025 +0100
Fix: ST_Transform does nothing when the CRS is declared on the attribute
type.
https://issues.apache.org/jira/browse/SIS-621
---
.../org/apache/sis/feature/AbstractFeature.java | 22 ++++++
.../main/org/apache/sis/feature/DenseFeature.java | 2 +-
.../main/org/apache/sis/feature/SparseFeature.java | 2 +-
.../org/apache/sis/storage/FeatureQueryTest.java | 83 ++++++++++++++++++++++
4 files changed, 107 insertions(+), 2 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
index e8d683edcf..3effa90bea 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
@@ -17,6 +17,7 @@
package org.apache.sis.feature;
import java.util.Objects;
+import java.util.Optional;
import java.util.Iterator;
import java.util.Collection;
import java.util.Collections;
@@ -558,6 +559,27 @@ public abstract class AbstractFeature implements Feature,
Serializable {
association.setValue((Feature) value);
}
+ /**
+ * Returns the default characteristic values as specified in the feature
type.
+ * This method is invoked when an individual property cannot have
characteristic.
+ * It happens with {@link DenseFeature} and {@link SparseFeature}
subclasses,
+ * which have optimization for the case where a feature contains only
values
+ * without the other information related to properties (such as
characteristics).
+ *
+ * @param property name of the property for which to get a
characteristic.
+ * @param characteristic name of the characteristic of the property of
the given name.
+ * @return default value of the specified characteristic on the specified
property.
+ * @throws PropertyNotFoundException if the {@code property} argument is
not the name of a property.
+ */
+ final Optional<?> getDefaultCharacteristicValue(final String property,
final String characteristic) {
+ final PropertyType p = type.getProperty(property);
+ if (p instanceof AttributeType<?>) {
+ return Optional.ofNullable(((AttributeType<?>)
p).characteristics().get(characteristic))
+ .map(AttributeType::getDefaultValue);
+ }
+ return Optional.empty();
+ }
+
/**
* Returns {@code true} if the caller can skip the call to {@link
#verifyPropertyValue(String, Object)}.
* This is a slight optimization for the case when we replaced an
attribute value by a new value of
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
index 38315596a9..a8f31630c4 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
@@ -259,7 +259,7 @@ final class DenseFeature extends AbstractFeature implements
CloneAccess {
if (properties instanceof Property[]) {
return super.getCharacteristicValue(property, characteristic);
}
- return Optional.empty();
+ return getDefaultCharacteristicValue(property, characteristic);
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
index f49577495f..b13a3c0118 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
@@ -323,7 +323,7 @@ final class SparseFeature extends AbstractFeature
implements CloneAccess {
if (valuesKind != VALUES) {
return super.getCharacteristicValue(property, characteristic);
}
- return Optional.empty();
+ return getDefaultCharacteristicValue(property, characteristic);
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
index 1897df8c5e..01afe9bf99 100644
---
a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
+++
b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
@@ -18,18 +18,28 @@ package org.apache.sis.storage;
import java.util.List;
import java.util.Arrays;
+import java.util.Set;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.stream.Collectors;
+import java.awt.geom.Point2D;
+import org.opengis.metadata.acquisition.GeometryType;
import org.apache.sis.feature.Features;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.feature.builder.AttributeRole;
import org.apache.sis.feature.internal.shared.AttributeConvention;
import org.apache.sis.filter.DefaultFilterFactory;
+import org.apache.sis.geometry.WraparoundMethod;
+import org.apache.sis.geometry.wrapper.Geometries;
+import org.apache.sis.geometry.wrapper.GeometryWrapper;
+import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.util.iso.Names;
// Test dependencies
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Disabled;
import static org.junit.jupiter.api.Assertions.*;
+import org.apache.sis.referencing.crs.HardCodedCRS;
import org.apache.sis.test.TestCase;
import static org.apache.sis.test.Assertions.assertSetEquals;
import static org.apache.sis.test.Assertions.assertSingleton;
@@ -42,6 +52,7 @@ import org.opengis.feature.PropertyType;
import org.opengis.feature.AttributeType;
import org.opengis.feature.IdentifiedType;
import org.opengis.feature.Operation;
+import org.opengis.feature.Attribute;
import org.opengis.filter.Expression;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
@@ -95,6 +106,34 @@ public final class FeatureQueryTest extends TestCase {
featureSet = new MemoryFeatureSet(null, type, Arrays.asList(features));
}
+ /**
+ * Creates a set of features with a geometry object.
+ * The points use (latitude, longitude) coordinates on a diagonal.
+ * The geometry library is specified by the {@link #library} field.
+ *
+ * @param library the library to use for creating geometry objects.
+ * @return the points created by this method in no particular order.
+ */
+ private Set<Point2D.Double> createFeaturesWithGeometry(final
GeometryLibrary library) {
+ final FeatureTypeBuilder ftb = new FeatureTypeBuilder(null, library,
null).setName("Test");
+
ftb.addAttribute(GeometryType.POINT).setCRS(HardCodedCRS.WGS84_LATITUDE_FIRST).setName("point");
+ final FeatureType type = ftb.build();
+ final var points = new HashSet<Point2D.Double>();
+ final Geometries<?> factory = Geometries.factory(library);
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
+ final var features = new Feature[4];
+ for (int i=0; i < features.length; i++) {
+ final var point = new Point2D.Double(-10 - i, 20 + i);
+ assertTrue(points.add(point));
+ final Feature f = type.newInstance();
+ f.setPropertyValue("point", factory.createPoint(point.x, point.y));
+ features[i] = f;
+ };
+ this.features = features;
+ featureSet = new MemoryFeatureSet(null, type, Arrays.asList(features));
+ return points;
+ }
+
/**
* Creates a set of features common to most tests.
* The feature type is composed of two attributes and one association.
@@ -451,4 +490,48 @@ public final class FeatureQueryTest extends TestCase {
var exception = assertThrows(UnsupportedQueryException.class,
this::executeAndGetFirst);
assertMessageContains(exception);
}
+
+ /**
+ * Tests {@code ST_Transform} with the <abbr>JTS</abbr> library.
+ *
+ * @throws DataStoreException if an error occurred while executing the
query.
+ */
+ public void testST_Transform_WithJTS() throws DataStoreException {
+ testST_Transform(GeometryLibrary.JTS);
+ }
+
+ /**
+ * Tests {@code ST_Transform} with the <abbr>ESRI</abbr> library.
+ *
+ * @throws DataStoreException if an error occurred while executing the
query.
+ */
+ @Disabled("Pending implementation of GeometryWrapper.transform in ESRI
wrappers.")
+ public void testST_Transform_WithESRI() throws DataStoreException {
+ testST_Transform(GeometryLibrary.ESRI);
+ }
+
+ /**
+ * Tests {@code ST_Transform} with the geometry library specified by
{@link #library}.
+ *
+ * @param library the library to use for creating geometry objects.
+ * @throws DataStoreException if an error occurred while executing the
query.
+ */
+ private void testST_Transform(final GeometryLibrary library) throws
DataStoreException {
+ final Set<Point2D.Double> points = createFeaturesWithGeometry(library);
+ final Geometries<?> factory = Geometries.factory(library);
+ final var ff = new DefaultFilterFactory.Features<>(factory.rootClass,
Object.class, WraparoundMethod.NONE);
+ final var transform = ff.function("ST_Transform",
ff.property("point"), ff.literal(HardCodedCRS.WGS84));
+ query.setProjection(new FeatureQuery.NamedExpression(transform));
+ final FeatureSet subset = query.execute(featureSet);
+ subset.features(false).forEach((f) -> {
+ final var property = (Attribute<?>) f.getProperty("point");
+ final GeometryWrapper point =
factory.castOrWrap(property.getValue());
+ assertEquals(HardCodedCRS.WGS84,
point.getCoordinateReferenceSystem());
+ final double[] coordinates = point.getPointCoordinates();
+ assertEquals(2, coordinates.length);
+ // Coordinate order should be swapped compared to the points
created by `createFeaturesWithGeometry(…)`.
+ assertTrue(points.remove(new Point2D.Double(coordinates[1],
coordinates[0])));
+ });
+ assertTrue(points.isEmpty()); // All points should have been found.
+ }
}