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.
+    }
 }

Reply via email to