This is an automated email from the ASF dual-hosted git repository.

afs pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git


The following commit(s) were added to refs/heads/main by this push:
     new 04c8e3c559 GH-4230: Preserve declared layout of nested WKT collections 
in geometryN
04c8e3c559 is described below

commit 04c8e3c5590d153fd02b0516ef0f5c2e1965c266
Author: Edmond Chuc <[email protected]>
AuthorDate: Sun Sep 27 22:53:46 2026 +1000

    GH-4230: Preserve declared layout of nested WKT collections in geometryN
    
    Previously, declared coordinate layout was only stashed in userData for
    empty aggregates. In DimensionInfo.find, inspecting the first member took
    precedence over userData. Consequently, selecting a nested collection with
    heterogeneous children (e.g., GEOMETRYCOLLECTION M containing a Point Z)
    caused the collection to inherit its first child's coordinate layout
    rather than preserving its own declared layout.
    
    Retain the declared coordinate layout on all aggregates during WKT parsing,
    and prioritize this retained layout over the first-member fallback in
    DimensionInfo.find. Add tests for nested collections with XY, M, Z, and ZM
    markers through geometryN selection and WKT round trips.
---
 .../geosparql/implementation/DimensionInfo.java    | 11 +++---
 .../implementation/parsers/wkt/WKTReader.java      | 11 +++---
 .../implementation/DimensionInfoTest.java          | 11 ++++++
 .../implementation/GeometryWrapperTest.java        | 41 ++++++++++++++++++++++
 .../implementation/parsers/wkt/WKTReaderTest.java  | 11 ++++++
 5 files changed, 75 insertions(+), 10 deletions(-)

diff --git 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/DimensionInfo.java
 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/DimensionInfo.java
index cac4b14ba5..c4601eb261 100644
--- 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/DimensionInfo.java
+++ 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/DimensionInfo.java
@@ -97,9 +97,8 @@ public class DimensionInfo implements Serializable {
 
     /**
      * Finds dimension metadata from coordinate sequences, preserving Z/M 
layouts
-     * even when the sequence is empty. Collections use their first member's
-     * layout, layout retained on an empty aggregate, or the fallback when they
-     * have no members.
+     * even when the sequence is empty. Collections use their retained layout
+     * when present, otherwise their first member's layout or the fallback.
      */
     public static DimensionInfo find(Geometry geometry, 
CoordinateSequenceDimensions fallback) {
         CoordinateSequence sequence = null;
@@ -109,11 +108,11 @@ public class DimensionInfo implements Serializable {
             sequence = line.getCoordinateSequence();
         } else if (geometry instanceof Polygon polygon) {
             sequence = polygon.getExteriorRing().getCoordinateSequence();
-        } else if (geometry instanceof GeometryCollection collection && 
collection.getNumGeometries() > 0) {
-            fallback = find(collection.getGeometryN(0), 
fallback).getDimensions();
         } else if (geometry.getUserData() instanceof 
CoordinateSequenceDimensions retained) {
-            // WKTReader stores parsed layout here; empty aggregates have no 
sequence.
+            // WKTReader stores the aggregate's declared layout here.
             fallback = retained;
+        } else if (geometry instanceof GeometryCollection collection && 
collection.getNumGeometries() > 0) {
+            fallback = find(collection.getGeometryN(0), 
fallback).getDimensions();
         }
         CoordinateSequenceDimensions dimensions = sequence == null ? fallback
             : findCoordinateSequenceDimensions(sequence.getDimension(), 
sequence.getDimension() - sequence.getMeasures());
diff --git 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/parsers/wkt/WKTReader.java
 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/parsers/wkt/WKTReader.java
index 009ea9b4da..96a8be29ea 100644
--- 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/parsers/wkt/WKTReader.java
+++ 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/parsers/wkt/WKTReader.java
@@ -55,7 +55,7 @@ public class WKTReader implements ParserReader {
     protected WKTReader(String geometryType, String dimensionString, String 
coordinates, String srsURI) {
         this.dims = convertDimensionString(dimensionString);
         this.geometry = buildGeometry(geometryType, coordinates);
-        retainEmptyAggregateLayout(geometry, dims);
+        retainAggregateLayout(geometry, dims);
         this.dimensionInfo = new DimensionInfo(dims, geometry.getDimension());
         this.srsURI = srsURI;
     }
@@ -150,9 +150,12 @@ public class WKTReader implements ParserReader {
         return geo;
     }
 
-    /** Empty Multi/collection has no sequence for Z/M/ZM; stash the parsed 
layout for DimensionInfo.find. */
-    private static void retainEmptyAggregateLayout(Geometry geometry, 
CoordinateSequenceDimensions dims) {
-        if (geometry instanceof GeometryCollection collection && 
collection.getNumGeometries() == 0) {
+    /**
+     * Aggregates do not have their own coordinate sequence; retain their 
declared
+     * layout for DimensionInfo.find.
+     */
+    private static void retainAggregateLayout(Geometry geometry, 
CoordinateSequenceDimensions dims) {
+        if (geometry instanceof GeometryCollection) {
             geometry.setUserData(dims);
         }
     }
diff --git 
a/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/DimensionInfoTest.java
 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/DimensionInfoTest.java
index 8abc3dd492..ea88b01a1f 100644
--- 
a/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/DimensionInfoTest.java
+++ 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/DimensionInfoTest.java
@@ -146,6 +146,17 @@ public class DimensionInfoTest {
         }
     }
 
+    @Test
+    public void 
testFindGeometryPrefersRetainedLayoutOverFirstCollectionMember() {
+        GeometryFactory factory = CustomGeometryFactory.theInstance();
+        Point elevated = factory.createPoint(new 
CustomCoordinateSequence(CoordinateSequenceDimensions.XYZ, "1 2 3"));
+        Geometry collection = factory.createGeometryCollection(new Geometry[] 
{ elevated });
+        collection.setUserData(CoordinateSequenceDimensions.XYM);
+
+        assertEquals(CoordinateSequenceDimensions.XYM,
+                DimensionInfo.find(collection, 
CoordinateSequenceDimensions.XY).getDimensions());
+    }
+
     @Test
     public void testFindGeometryUsesFirstCollectionMemberLayout() {
         GeometryFactory factory = CustomGeometryFactory.theInstance();
diff --git 
a/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/GeometryWrapperTest.java
 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/GeometryWrapperTest.java
index 066ba38802..c260406a62 100644
--- 
a/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/GeometryWrapperTest.java
+++ 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/GeometryWrapperTest.java
@@ -558,6 +558,47 @@ public class GeometryWrapperTest {
         assertThrows(IllegalArgumentException.class, () -> 
source.getGeometryN(3));
     }
 
+    @Test
+    public void testGetGeometryNPreservesNestedCollectionLayout() {
+        String[] members = {
+            "GEOMETRYCOLLECTION (POINT Z (1 2 3), POINT M (4 5 6))",
+            "GEOMETRYCOLLECTION M (POINT Z (1 2 3), POINT M (4 5 6))",
+            "GEOMETRYCOLLECTION Z (POINT M (1 2 3), POINT Z (4 5 6))",
+            "GEOMETRYCOLLECTION ZM (POINT Z (1 2 3), POINT M (4 5 6))"
+        };
+        CoordinateSequenceDimensions[] expected = {
+            CoordinateSequenceDimensions.XY,
+            CoordinateSequenceDimensions.XYM,
+            CoordinateSequenceDimensions.XYZ,
+            CoordinateSequenceDimensions.XYZM
+        };
+        CoordinateSequenceDimensions[] firstMembers = {
+            CoordinateSequenceDimensions.XYZ,
+            CoordinateSequenceDimensions.XYZ,
+            CoordinateSequenceDimensions.XYM,
+            CoordinateSequenceDimensions.XYZ
+        };
+        CoordinateSequenceDimensions[] secondMembers = {
+            CoordinateSequenceDimensions.XYM,
+            CoordinateSequenceDimensions.XYM,
+            CoordinateSequenceDimensions.XYZ,
+            CoordinateSequenceDimensions.XYM
+        };
+
+        for (int i = 0; i < members.length; i++) {
+            GeometryWrapper selected = GeometryWrapper.extract(
+                    "GEOMETRYCOLLECTION (" + members[i] + ")", 
WKTDatatype.URI).getGeometryN(1);
+
+            assertEquals(members[i], expected[i], 
selected.getCoordinateSequenceDimensions());
+            assertEquals(members[i], expected[i], GeometryWrapper.extract(
+                    selected.getLexicalForm(), 
WKTDatatype.URI).getCoordinateSequenceDimensions());
+            assertEquals(members[i], firstMembers[i],
+                    
selected.getGeometryN(1).getCoordinateSequenceDimensions());
+            assertEquals(members[i], secondMembers[i],
+                    
selected.getGeometryN(2).getCoordinateSequenceDimensions());
+        }
+    }
+
     @Test
     public void testGetGeometryNUsesSelectedMemberLayoutAndTopology() {
         GeometryWrapper source = GeometryWrapper.extract(
diff --git 
a/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/parsers/wkt/WKTReaderTest.java
 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/parsers/wkt/WKTReaderTest.java
index 78cd465cb5..2f32467237 100644
--- 
a/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/parsers/wkt/WKTReaderTest.java
+++ 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/parsers/wkt/WKTReaderTest.java
@@ -367,6 +367,17 @@ public class WKTReaderTest {
         assertEquals(expResult, result);
     }
 
+    @Test
+    public void testExtractNonEmptyCollectionRetainsDeclaredLayout() {
+        Geometry collection = WKTReader.extract(
+                "GEOMETRYCOLLECTION M (POINT Z (1 2 3))").getGeometry();
+
+        assertEquals(CoordinateSequenceDimensions.XYM,
+                DimensionInfo.find(collection, 
CoordinateSequenceDimensions.XY).getDimensions());
+        assertEquals(CoordinateSequenceDimensions.XYZ,
+                DimensionInfo.find(collection.getGeometryN(0), 
CoordinateSequenceDimensions.XY).getDimensions());
+    }
+
     @Test
     public void testExtractMultiGeometriesWithEmptyMembers() {
         String[] wkts = {

Reply via email to