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 e1e21b8c58 GH-4233: Add GeoSPARQL 1.1 centroid function
e1e21b8c58 is described below

commit e1e21b8c586ea9f54f78a3d5d0017fcd1a9081df
Author: Edmond Chuc <[email protected]>
AuthorDate: Sun Sep 27 01:49:55 2026 +1000

    GH-4233: Add GeoSPARQL 1.1 centroid function
---
 .../filter_functions/CentroidFF.java               |  44 ++++
 .../geosparql/implementation/GeometryWrapper.java  |  35 +++
 .../function_registration/NonTopological.java      |   2 +
 .../geosparql/implementation/vocabulary/Geof.java  |   1 +
 .../filter_functions/CentroidFFTest.java           | 244 +++++++++++++++++++++
 .../implementation/GeometryCentroidTest.java       | 104 +++++++++
 6 files changed, 430 insertions(+)

diff --git 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/geof/nontopological/filter_functions/CentroidFF.java
 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/geof/nontopological/filter_functions/CentroidFF.java
new file mode 100644
index 0000000000..8ef27e81e5
--- /dev/null
+++ 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/geof/nontopological/filter_functions/CentroidFF.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *   SPDX-License-Identifier: Apache-2.0
+ */
+package org.apache.jena.geosparql.geof.nontopological.filter_functions;
+
+import org.apache.jena.datatypes.DatatypeFormatException;
+import org.apache.jena.geosparql.implementation.GeometryWrapper;
+import org.apache.jena.sparql.expr.ExprEvalException;
+import org.apache.jena.sparql.expr.NodeValue;
+import org.apache.jena.sparql.function.FunctionBase1;
+
+/** Implements geof:centroid. */
+public class CentroidFF extends FunctionBase1 {
+
+    @Override
+    public NodeValue exec(NodeValue v) {
+
+        try {
+            GeometryWrapper geometry = GeometryWrapper.extract(v);
+            GeometryWrapper centroid = geometry.centroid();
+            return centroid.asNodeValue();
+        } catch (DatatypeFormatException ex) {
+            throw new ExprEvalException(ex.getMessage(), ex);
+        }
+    }
+
+}
diff --git 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/GeometryWrapper.java
 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/GeometryWrapper.java
index 9336bcc664..d609982e5e 100644
--- 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/GeometryWrapper.java
+++ 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/GeometryWrapper.java
@@ -46,6 +46,7 @@ import org.apache.jena.rdf.model.ResourceFactory;
 import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateXY;
 import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryFactory;
@@ -772,6 +773,40 @@ public class GeometryWrapper implements Serializable {
         return new GeometryWrapper(parsingGeo, xyGeo, srsInfo.getSrsURI(), 
geometryDatatypeURI, dimensionInfo);
     }
 
+    /**
+     * Returns the planar centroid in the source SRS and datatype. A result in 
a
+     * three-dimensional CRS has {@code Z=0}; {@code Z} and {@code M} are 
ignored
+     * in the calculation. Geographic coordinates are treated as planar
+     * coordinates, without reprojection.
+     *
+     * @throws DatatypeFormatException if a non-empty GML centroid has a source
+     * CRS coordinate dimension other than 2 or 3
+     */
+    public GeometryWrapper centroid() {
+        Point centroid = xyGeometry.getCentroid();
+        GeometryFactory factory = CustomGeometryFactory.theInstance();
+        int crsDimension = 
srsInfo.getCrs().getCoordinateSystem().getDimension();
+        Point xyCentroid;
+        if (centroid.isEmpty()) {
+            xyCentroid = factory.createPoint(new 
CustomCoordinateSequence(CoordinateSequenceDimensions.XY));
+        } else if (crsDimension == 3) {
+            xyCentroid = factory.createPoint(new Coordinate(centroid.getX(), 
centroid.getY(), 0));
+        } else {
+            xyCentroid = factory.createPoint(new CoordinateXY(centroid.getX(), 
centroid.getY()));
+        }
+        // GML positions follow the CRS dimension. The centroid supplies XY or
+        // XYZ with Z=0, but no additional ordinate.
+        if (!xyCentroid.isEmpty()
+                && GMLDatatype.URI.equals(geometryDatatypeURI)
+                && crsDimension != 2 && crsDimension != 3) {
+            throw new DatatypeFormatException(
+                "A centroid cannot be represented as GML in the source CRS.");
+        }
+        Geometry parsingCentroid = GeometryReverse.check(xyCentroid, srsInfo);
+        DimensionInfo centroidDimensions = 
DimensionInfo.find(xyCentroid.getCoordinate(), xyCentroid);
+        return new GeometryWrapper(parsingCentroid, xyCentroid, getSrsURI(), 
geometryDatatypeURI, centroidDimensions);
+    }
+
     /**
      *
      * @param targetGeometry
diff --git 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/function_registration/NonTopological.java
 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/function_registration/NonTopological.java
index 4797384dad..fd893806ae 100644
--- 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/function_registration/NonTopological.java
+++ 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/function_registration/NonTopological.java
@@ -23,6 +23,7 @@ package 
org.apache.jena.geosparql.implementation.function_registration;
 import 
org.apache.jena.geosparql.geof.nontopological.filter_functions.AsGeoJSONFF;
 import 
org.apache.jena.geosparql.geof.nontopological.filter_functions.BoundaryFF;
 import org.apache.jena.geosparql.geof.nontopological.filter_functions.BufferFF;
+import 
org.apache.jena.geosparql.geof.nontopological.filter_functions.CentroidFF;
 import 
org.apache.jena.geosparql.geof.nontopological.filter_functions.ConvexHullFF;
 import 
org.apache.jena.geosparql.geof.nontopological.filter_functions.DifferenceFF;
 import 
org.apache.jena.geosparql.geof.nontopological.filter_functions.DistanceFF;
@@ -52,6 +53,7 @@ public class NonTopological {
         // Non Topological Filter Functions
         registry.put(Geof.BOUNDARY_NAME, BoundaryFF.class);
         registry.put(Geof.BUFFER_NAME, BufferFF.class);
+        registry.put(Geof.CENTROID_NAME, CentroidFF.class);
         registry.put(Geof.CONVEXHULL_NAME, ConvexHullFF.class);
         registry.put(Geof.DIFFERENCE_NAME, DifferenceFF.class);
         registry.put(Geof.DISTANCE_NAME, DistanceFF.class);
diff --git 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/vocabulary/Geof.java
 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/vocabulary/Geof.java
index b2a018e44b..00d45ecf9b 100644
--- 
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/vocabulary/Geof.java
+++ 
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/vocabulary/Geof.java
@@ -71,6 +71,7 @@ public interface Geof {
     public static final String BUFFER_NAME = GEOF_URI + "buffer";
     public static final String DIFFERENCE_NAME = GEOF_URI + "difference";
     public static final String BOUNDARY_NAME = GEOF_URI + "boundary";
+    public static final String CENTROID_NAME = GEOF_URI + "centroid";
     public static final String CONVEXHULL_NAME = GEOF_URI + "convexHull";
     public static final String GETSRID_NAME = GEOF_URI + "getSRID";
 
diff --git 
a/jena-geosparql/src/test/java/org/apache/jena/geosparql/geof/nontopological/filter_functions/CentroidFFTest.java
 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/geof/nontopological/filter_functions/CentroidFFTest.java
new file mode 100644
index 0000000000..685bc33cbc
--- /dev/null
+++ 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/geof/nontopological/filter_functions/CentroidFFTest.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *   SPDX-License-Identifier: Apache-2.0
+ */
+package org.apache.jena.geosparql.geof.nontopological.filter_functions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.jena.geosparql.configuration.GeoSPARQLConfig;
+import org.apache.jena.geosparql.implementation.GeometryWrapper;
+import org.apache.jena.geosparql.implementation.datatype.GMLDatatype;
+import org.apache.jena.geosparql.implementation.datatype.WKTDatatype;
+import 
org.apache.jena.geosparql.implementation.jts.CoordinateSequenceDimensions;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.NodeFactory;
+import org.apache.jena.query.QueryBuildException;
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.query.QuerySolution;
+import org.apache.jena.query.ResultSet;
+import org.apache.jena.rdf.model.ModelFactory;
+import org.apache.jena.sparql.expr.ExprEvalException;
+import org.apache.jena.sparql.expr.NodeValue;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class CentroidFFTest {
+    @BeforeClass
+    public static void setup() {
+        GeoSPARQLConfig.setupNoIndex();
+    }
+
+    @Test
+    public void pointAndMultiPointUseCoordinateMean() {
+        assertCentroid("POINT (2 4)", 2, 4);
+        assertCentroid("MULTIPOINT ((0 0), (6 0), (0 6))", 2, 2);
+    }
+
+    @Test
+    public void lineCentroidIsLengthWeighted() {
+        assertCentroid("LINESTRING (0 0, 8 0, 8 2)", 4.8, 0.2);
+        assertCentroid("MULTILINESTRING ((0 0, 8 0), (8 0, 8 2))", 4.8, 0.2);
+    }
+
+    @Test
+    public void polygonCentroidUsesArea() {
+        assertCentroid("POLYGON ((0 0, 6 0, 0 6, 0 0))", 2, 2);
+    }
+
+    @Test
+    public void polygonHolesSubtractArea() {
+        // Outer area 16 centred at (2,2), hole area 1 centred at (1.5,1.5).
+        assertCentroid("POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 1 2, 2 2, 2 
1, 1 1))",
+                       30.5 / 15, 30.5 / 15);
+    }
+
+    @Test
+    public void multiPolygonCentroidIsAreaWeighted() {
+        assertCentroid("MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((4 0, 8 0, 
8 2, 4 2, 4 0)))",
+                       13.0 / 3, 1);
+    }
+
+    @Test
+    public void mixedCollectionUsesHighestDimensionalMembers() {
+        assertCentroid("GEOMETRYCOLLECTION (POINT (90 90), LINESTRING (0 0, 8 
0))", 4, 0);
+        assertCentroid("GEOMETRYCOLLECTION (POINT (90 90), POLYGON ((0 0, 6 0, 
0 6, 0 0)))", 2, 2);
+    }
+
+    @Test
+    public void resultDropsZAndMAndHasPointMetadata() {
+        for (String wkt : new String[] { "POINT Z (2 4 99)", "POINT M (2 4 
99)", "POINT ZM (2 4 99 100)",
+                                        "LINESTRING ZM (0 4 9 8, 4 4 7 6)" }) {
+            assertCentroid(wkt, 2, 4);
+        }
+    }
+
+    @Test
+    public void authorityAxisOrderIsRetainedInTheResult() {
+        String crs = "<http://www.opengis.net/def/crs/EPSG/0/4326> ";
+        GeometryWrapper result = centroid(crs + "LINESTRING (10 100, 20 120)");
+        assertEquals("http://www.opengis.net/def/crs/EPSG/0/4326";, 
result.getSrsURI());
+        assertEquals(15, result.getParsingGeometry().getCoordinate().getX(), 
0);
+        assertEquals(110, result.getParsingGeometry().getCoordinate().getY(), 
0);
+        assertEquals(110, result.getXYGeometry().getCoordinate().getX(), 0);
+        assertEquals(15, result.getXYGeometry().getCoordinate().getY(), 0);
+    }
+
+    @Test
+    public void projectedCrsIsPreserved() {
+        String crs = "<http://www.opengis.net/def/crs/EPSG/0/27700> ";
+        GeometryWrapper result = centroid(crs + "LINESTRING (100 200, 300 
400)");
+        assertEquals("http://www.opengis.net/def/crs/EPSG/0/27700";, 
result.getSrsURI());
+        assertEquals(200, result.getXYGeometry().getCoordinate().getX(), 0);
+        assertEquals(300, result.getXYGeometry().getCoordinate().getY(), 0);
+    }
+
+    @Test
+    public void geographicCentroidIsPlanar() {
+        assertCentroid("MULTIPOINT ((170 10), (-170 10))", 0, 10);
+    }
+
+    @Test
+    public void emptyInputsReturnAnEmptyXyPoint() {
+        for (String wkt : new String[] { "POINT EMPTY", "LINESTRING EMPTY", 
"POLYGON EMPTY",
+                "MULTIPOINT EMPTY", "MULTILINESTRING EMPTY", "MULTIPOLYGON 
EMPTY", "GEOMETRYCOLLECTION EMPTY",
+                "POINT Z EMPTY", "POINT M EMPTY", "POINT ZM EMPTY" }) {
+            GeometryWrapper result = centroid(wkt);
+            assertPointMetadata(result);
+            assertTrue(wkt, result.isEmpty());
+            assertEquals("POINT EMPTY", 
result.asNodeValue().asNode().getLiteralLexicalForm());
+        }
+    }
+
+    @Test
+    public void gmlResultRetainsDatatypeAndAxisOrder() {
+        String gml = """
+            '<gml:LineString xmlns:gml="http://www.opengis.net/gml/3.2";
+                srsName="http://www.opengis.net/def/crs/EPSG/0/4326";>
+              <gml:posList>10 100 20 120</gml:posList>
+            </gml:LineString>'^^geo:gmlLiteral
+            """.replace("\n", " ");
+        Node node = evaluate("geof:centroid(" + gml + ")");
+        assertNotNull(node);
+        assertEquals("http://www.opengis.net/ont/geosparql#gmlLiteral";, 
node.getLiteralDatatypeURI());
+        GeometryWrapper result = GeometryWrapper.extract(node);
+        assertPointMetadata(result);
+        assertEquals("http://www.opengis.net/def/crs/EPSG/0/4326";, 
result.getSrsURI());
+        assertEquals(15, result.getParsingGeometry().getCoordinate().getX(), 
0);
+        assertEquals(110, result.getParsingGeometry().getCoordinate().getY(), 
0);
+    }
+
+    @Test
+    public void threeDimensionalWktCrsReturnsAProjectedPoint() {
+        GeometryWrapper result = 
centroid("<http://www.opengis.net/def/crs/EPSG/0/4979> POINT Z (10 100 7)");
+        assertEquals("Point", result.getGeometryType());
+        assertEquals(0, result.getTopologicalDimension());
+        assertEquals(CoordinateSequenceDimensions.XYZ, 
result.getCoordinateSequenceDimensions());
+        assertEquals("http://www.opengis.net/def/crs/EPSG/0/4979";, 
result.getSrsURI());
+        assertEquals(10, result.getParsingGeometry().getCoordinate().getX(), 
0);
+        assertEquals(100, result.getParsingGeometry().getCoordinate().getY(), 
0);
+        assertEquals(0, result.getParsingGeometry().getCoordinate().getZ(), 0);
+    }
+
+    @Test
+    public void threeDimensionalGmlCentroidComposesWithOtherFunctions() {
+        String input = """
+            '<gml:LineString xmlns:gml="http://www.opengis.net/gml/3.2";
+                srsName="http://www.opengis.net/def/crs/EPSG/0/4979";>
+              <gml:posList>10 100 7 20 120 9</gml:posList>
+            </gml:LineString>'^^geo:gmlLiteral
+            """.replace("\n", " ");
+        Node node = evaluate("geof:centroid(" + input + ")");
+        assertNotNull(node);
+        assertEquals(GMLDatatype.URI, node.getLiteralDatatypeURI());
+        GeometryWrapper result = GeometryWrapper.extract(node);
+        assertEquals("http://www.opengis.net/def/crs/EPSG/0/4979";, 
result.getSrsURI());
+        assertEquals(CoordinateSequenceDimensions.XYZ, 
result.getCoordinateSequenceDimensions());
+        assertEquals(15, result.getParsingGeometry().getCoordinate().getX(), 
0);
+        assertEquals(110, result.getParsingGeometry().getCoordinate().getY(), 
0);
+        assertEquals(0, result.getParsingGeometry().getCoordinate().getZ(), 0);
+        assertEquals(NodeValue.makeInteger(3).asNode(), 
evaluate("geof:coordinateDimension(geof:centroid(" + input + "))"));
+    }
+
+    @Test
+    public void centroidComposesWithOtherGeometryFunctions() {
+        assertEquals(NodeValue.makeInteger(0).asNode(), evaluate(
+            "geof:dimension(geof:centroid('POLYGON ((0 0, 6 0, 0 6, 0 
0))'^^geo:wktLiteral))"));
+    }
+
+    @Test
+    public void invalidArgumentsRaiseExpressionErrors() {
+        CentroidFF function = new CentroidFF();
+        for (NodeValue value : new NodeValue[] { NodeValue.makeInteger(42), 
NodeValue.makeString("POINT (1 2)"),
+                NodeValue.makeNode(NodeFactory.createURI("urn:geometry")), 
NodeValue.makeNode("invalid", WKTDatatype.INSTANCE) }) {
+            assertThrows(ExprEvalException.class, () -> function.exec(value));
+        }
+        for (String value : new String[] { "42", "'POINT (1 2)'", 
"<urn:geometry>", "'invalid'^^geo:wktLiteral", "?missing" }) {
+            assertNull(evaluate("geof:centroid(" + value + ")"));
+        }
+    }
+
+    @Test
+    public void wrongArityIsRejectedAtQueryBuild() {
+        assertThrows(QueryBuildException.class, () -> 
evaluate("geof:centroid()"));
+        assertThrows(QueryBuildException.class,
+                     () -> evaluate("geof:centroid('POINT 
EMPTY'^^geo:wktLiteral, 1)"));
+    }
+
+    private static void assertCentroid(String wkt, double x, double y) {
+        GeometryWrapper result = centroid(wkt);
+        assertPointMetadata(result);
+        assertFalse(wkt, result.isEmpty());
+        assertEquals(wkt, x, result.getXYGeometry().getCoordinate().getX(), 
1e-6);
+        assertEquals(wkt, y, result.getXYGeometry().getCoordinate().getY(), 
1e-6);
+    }
+
+    private static void assertPointMetadata(GeometryWrapper result) {
+        assertEquals("Point", result.getGeometryType());
+        assertEquals(0, result.getTopologicalDimension());
+        assertEquals(CoordinateSequenceDimensions.XY, 
result.getCoordinateSequenceDimensions());
+    }
+
+    private static GeometryWrapper centroid(String wkt) {
+        Node node = evaluate("geof:centroid('" + wkt + "'^^geo:wktLiteral)");
+        assertNotNull(wkt, node);
+        assertEquals(WKTDatatype.URI, node.getLiteralDatatypeURI());
+        return GeometryWrapper.extract(node);
+    }
+
+    private static Node evaluate(String expression) {
+        String query = """
+            PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
+            PREFIX geo: <http://www.opengis.net/ont/geosparql#>
+            SELECT ?result WHERE { BIND(%s AS ?result) }
+            """.formatted(expression);
+        try (QueryExecution execution = QueryExecution.create(query, 
ModelFactory.createDefaultModel())) {
+            ResultSet results = execution.execSelect();
+            assertTrue(expression, results.hasNext());
+            QuerySolution solution = results.next();
+            assertFalse(expression, results.hasNext());
+            return solution.contains("result") ? 
solution.get("result").asNode() : null;
+        }
+    }
+}
diff --git 
a/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/GeometryCentroidTest.java
 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/GeometryCentroidTest.java
new file mode 100644
index 0000000000..a22eb1c9a4
--- /dev/null
+++ 
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/implementation/GeometryCentroidTest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *   SPDX-License-Identifier: Apache-2.0
+ */
+package org.apache.jena.geosparql.implementation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.jena.geosparql.implementation.datatype.GMLDatatype;
+import org.apache.jena.geosparql.implementation.datatype.WKTDatatype;
+import 
org.apache.jena.geosparql.implementation.jts.CoordinateSequenceDimensions;
+import org.junit.Test;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+
+public class GeometryCentroidTest {
+    private static final String EMPTY_GML_POLYGON_EPSG_4979 =
+            "<gml:Polygon xmlns:gml=\"http://www.opengis.net/gml/3.2\"; 
srsName=\"http://www.opengis.net/def/crs/EPSG/0/4979\"/>";
+    private static final String GML_POINT_Z_EPSG_4979 =
+            "<gml:Point xmlns:gml=\"http://www.opengis.net/gml/3.2\"; 
srsName=\"http://www.opengis.net/def/crs/EPSG/0/4979\";><gml:pos>10 100 
7</gml:pos></gml:Point>";
+
+    @Test
+    public void ordinaryJtsGeometryProducesASerializableXyPoint() {
+        GeometryFactory factory = new GeometryFactory();
+        Geometry line = factory.createLineString(new Coordinate[] { new 
Coordinate(0, 0, 7), new Coordinate(4, 0, 9) });
+        GeometryWrapper result = new GeometryWrapper(line, 
WKTDatatype.URI).centroid();
+        assertEquals(CoordinateSequenceDimensions.XY, 
result.getCoordinateSequenceDimensions());
+        assertEquals(0, result.getTopologicalDimension());
+        GeometryWrapper reparsed = 
GeometryWrapper.extract(result.asNodeValue());
+        assertEquals(2, reparsed.getXYGeometry().getCoordinate().getX(), 0);
+        assertEquals(0, reparsed.getXYGeometry().getCoordinate().getY(), 0);
+    }
+
+    @Test
+    public void nestedCollectionUsesNonemptyHighestDimensionalMembers() {
+        GeometryFactory factory = new GeometryFactory();
+        Geometry line = factory.createLineString(new Coordinate[] { new 
Coordinate(0, 0), new Coordinate(4, 0) });
+        Geometry nested = factory.createGeometryCollection(new Geometry[] { 
line, factory.createPolygon() });
+        Geometry collection = factory.createGeometryCollection(new Geometry[] {
+            factory.createPoint(new Coordinate(90, 90)), nested
+        });
+        GeometryWrapper source = new GeometryWrapper(collection, 
WKTDatatype.URI);
+        GeometryWrapper result = source.centroid();
+        assertEquals(2, result.getXYGeometry().getCoordinate().getX(), 0);
+        assertEquals(0, result.getXYGeometry().getCoordinate().getY(), 0);
+        assertEquals(2, source.getParsingGeometry().getNumGeometries());
+    }
+
+    @Test
+    public void ordinaryJtsEmptyGeometryProducesASerializableEmptyPoint() {
+        GeometryWrapper result = new GeometryWrapper(new 
GeometryFactory().createPolygon(), WKTDatatype.URI).centroid();
+        assertEquals(CoordinateSequenceDimensions.XY, 
result.getCoordinateSequenceDimensions());
+        assertEquals("Point", result.getGeometryType());
+        assertTrue(GeometryWrapper.extract(result.asNodeValue()).isEmpty());
+    }
+
+    @Test
+    public void emptyGmlInAThreeDimensionalCrsProducesAnEmptyPoint() {
+        GeometryWrapper result = 
GeometryWrapper.extract(EMPTY_GML_POLYGON_EPSG_4979, 
GMLDatatype.URI).centroid();
+        assertEquals(CoordinateSequenceDimensions.XY, 
result.getCoordinateSequenceDimensions());
+        assertEquals("Point", result.getGeometryType());
+        assertEquals(GMLDatatype.URI, result.getGeometryDatatypeURI());
+        assertEquals("http://www.opengis.net/def/crs/EPSG/0/4979";, 
result.getSrsURI());
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void threeDimensionalGmlCentroidHasZeroHeightAndCanBeReparsed() {
+        GeometryWrapper source = 
GeometryWrapper.extract(GML_POINT_Z_EPSG_4979, GMLDatatype.URI);
+        GeometryWrapper result = source.centroid();
+        assertEquals(GMLDatatype.URI, result.getGeometryDatatypeURI());
+        assertEquals("http://www.opengis.net/def/crs/EPSG/0/4979";, 
result.getSrsURI());
+        assertEquals(CoordinateSequenceDimensions.XYZ, 
result.getCoordinateSequenceDimensions());
+        assertEquals(DimensionInfo.XYZ_POINT, result.getDimensionInfo());
+        assertEquals(10, result.getParsingGeometry().getCoordinate().getX(), 
0);
+        assertEquals(100, result.getParsingGeometry().getCoordinate().getY(), 
0);
+        assertEquals(0, result.getParsingGeometry().getCoordinate().getZ(), 0);
+
+        GeometryWrapper reparsed = 
GeometryWrapper.extract(result.asNodeValue());
+        assertEquals(result.getSrsURI(), reparsed.getSrsURI());
+        assertEquals(DimensionInfo.XYZ_POINT, reparsed.getDimensionInfo());
+        assertEquals(10, reparsed.getParsingGeometry().getCoordinate().getX(), 
0);
+        assertEquals(100, 
reparsed.getParsingGeometry().getCoordinate().getY(), 0);
+        assertEquals(0, reparsed.getParsingGeometry().getCoordinate().getZ(), 
0);
+    }
+}

Reply via email to