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);
+ }
+}