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

Reply via email to