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 = {