This is an automated email from the ASF dual-hosted git repository.
jiayu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git
The following commit(s) were added to refs/heads/master by this push:
new 654806938a [GH-2603] Fix ST_AreaSpheroid for polygons with holes
(#2607)
654806938a is described below
commit 654806938a2216e9a96f9d0c13ff5fd92e4ffedb
Author: Kristin Cowalcijk <[email protected]>
AuthorDate: Tue Feb 3 00:45:34 2026 +0800
[GH-2603] Fix ST_AreaSpheroid for polygons with holes (#2607)
---
.../org/apache/sedona/common/sphere/Spheroid.java | 35 ++++++++++++++--------
.../org/apache/sedona/common/FunctionsTest.java | 26 ++++++++++++++++
2 files changed, 49 insertions(+), 12 deletions(-)
diff --git a/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java
b/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java
index 546fec4f84..758670c06f 100644
--- a/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java
+++ b/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java
@@ -27,6 +27,7 @@ import net.sf.geographiclib.PolygonResult;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Polygon;
public class Spheroid {
@@ -151,19 +152,17 @@ public class Spheroid {
*/
public static double area(Geometry geom) {
String geomType = geom.getGeometryType();
- if (geomType.equals("Polygon")) {
- PolygonArea p = new PolygonArea(Geodesic.WGS84, false);
- Coordinate[] coordinates = geom.getCoordinates();
- for (int i = 0; i < coordinates.length; i++) {
- double lon = coordinates[i].getX();
- double lat = coordinates[i].getY();
- p.AddPoint(lat, lon);
+ if (geomType.equals(Geometry.TYPENAME_POLYGON)) {
+ Polygon poly = (Polygon) geom;
+
+ double area = ringArea(poly.getExteriorRing());
+ for (int i = 0; i < poly.getNumInteriorRing(); i++) {
+ area -= ringArea(poly.getInteriorRingN(i));
}
- PolygonResult compute = p.Compute();
- // The area is negative if the polygon is oriented clockwise
- // We make sure that all area are positive
- return abs(compute.area);
- } else if (geomType.equals("MultiPolygon") ||
geomType.equals("GeometryCollection")) {
+
+ return Math.max(0.0, area);
+ } else if (geomType.equals(Geometry.TYPENAME_MULTIPOLYGON)
+ || geomType.equals(Geometry.TYPENAME_GEOMETRYCOLLECTION)) {
double area = 0.0;
for (int i = 0; i < geom.getNumGeometries(); i++) {
area += area(geom.getGeometryN(i));
@@ -174,6 +173,18 @@ public class Spheroid {
}
}
+ private static double ringArea(LinearRing ring) {
+ PolygonArea p = new PolygonArea(Geodesic.WGS84, false);
+ Coordinate[] coordinates = ring.getCoordinates();
+ for (int i = 0; i < coordinates.length; i++) {
+ double lon = coordinates[i].getX();
+ double lat = coordinates[i].getY();
+ p.AddPoint(lat, lon);
+ }
+ PolygonResult compute = p.Compute();
+ return abs(compute.area);
+ }
+
public static Double angularWidth(Envelope envelope) {
double lon1 = envelope.getMinX();
double lon2 = envelope.getMaxX();
diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
index c4e6325143..ec7fbe00fe 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -35,6 +35,7 @@ import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.projection.ProjectionException;
import org.junit.Test;
import org.locationtech.jts.geom.*;
+import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
import org.locationtech.jts.io.ParseException;
@@ -1744,6 +1745,31 @@ public class FunctionsTest extends TestBase {
GeometryCollection geometryCollection =
GEOMETRY_FACTORY.createGeometryCollection(new Geometry[] {point,
polygon2, polygon3});
assertEquals(4.036497016235249E11, Spheroid.area(geometryCollection), 0.1);
+
+ // Polygon with hole: ensure interior rings subtract from total area
+ Polygon shell = GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 0, 10, 10,
10, 10, 0, 0, 0));
+ Polygon hole = GEOMETRY_FACTORY.createPolygon(coordArray(2, 2, 2, 8, 8, 8,
8, 2, 2, 2));
+ Polygon polygonWithHole =
+ GEOMETRY_FACTORY.createPolygon(
+ (LinearRing) shell.getExteriorRing(),
+ new LinearRing[] {(LinearRing) hole.getExteriorRing()});
+ assertTrue(Spheroid.area(polygonWithHole) > 0);
+ assertTrue(Spheroid.area(polygonWithHole) < Spheroid.area(shell));
+ }
+
+ // Temporary regression test for https://github.com/apache/sedona/issues/2603
+ @Test
+ public void spheroidAreaRegressionGH2603() throws ParseException {
+ String wkt =
+ "POLYGON((6.8782696039776 49.2766437532092,6.87823957674903
49.2766222406843,6.87815807078698 49.2765638879958,6.87804125519739
49.2764802462174,6.87799662173375 49.2764482636995,6.87797514117841
49.2764339340827,6.87813462196488 49.2763400751467,6.87818408824614
49.2763110269219,6.87825299096468 49.2762705688469,6.87853657017451
49.2761040516002,6.8786024223574 49.2761481365701,6.87859913601082
49.2761500643444,6.87873887570276 49.2762627881616,6.87878141030736
49.2763020366733, [...]
+ double expectedPostgisArea = 1232.39;
+
+ Geometry geom = wktReader.read(wkt);
+ geom.setSRID(4326);
+
+ double actual = Spheroid.area(geom);
+ double delta = Math.max(1e-6, expectedPostgisArea * 1e-3);
+ assertEquals(expectedPostgisArea, actual, delta);
}
@Test