This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 926b0a4071 feat(Geometry): add spherical triangle area and spherical 
excess methods
926b0a4071 is described below

commit 926b0a4071e2f7a17f1ef7b3cb8a52e1205cc7ea
Author: jsorel <[email protected]>
AuthorDate: Wed Aug 5 14:02:21 2026 +0200

    feat(Geometry): add spherical triangle area and spherical excess methods
---
 .../geometries/spherical/SphericalTriangle.java    | 39 ++++++++++++++++++++++
 .../spherical/SphericalTriangleTest.java           | 39 ++++++++++++++++++----
 2 files changed, 71 insertions(+), 7 deletions(-)

diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/spherical/SphericalTriangle.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/spherical/SphericalTriangle.java
index e5c5232e4a..3f36848cf5 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/spherical/SphericalTriangle.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/spherical/SphericalTriangle.java
@@ -80,6 +80,38 @@ public final class SphericalTriangle {
         return vecA.copy().add(vecB).add(vecC).normalize();
     }
 
+    /**
+     * Compute the spherical excess of this triangle : the sum of its three
+     * angles minus PI.
+     *
+     * @see <a 
href="https://mathworld.wolfram.com/SphericalExcess.html";>Spherical Excess</a>
+     * @return spherical excess in radians
+     */
+    public double getSphericalExcess() {
+        final double cosA = clamp(vecB.dot(vecC));
+        final double cosB = clamp(vecC.dot(vecA));
+        final double cosC = clamp(vecA.dot(vecB));
+        final double sinA = Math.sqrt(1 - cosA * cosA);
+        final double sinB = Math.sqrt(1 - cosB * cosB);
+        final double sinC = Math.sqrt(1 - cosC * cosC);
+
+        final double angleA = Math.acos(clamp((cosA - cosB * cosC) / (sinB * 
sinC)));
+        final double angleB = Math.acos(clamp((cosB - cosC * cosA) / (sinC * 
sinA)));
+        final double angleC = Math.acos(clamp((cosC - cosA * cosB) / (sinA * 
sinB)));
+
+        return angleA + angleB + angleC - Math.PI;
+    }
+
+    /**
+     * Compute the area of this triangle, using the sphere radius.
+     *
+     * @return triangle area, in the sphere radius squared units
+     */
+    public double getArea() {
+        final double radius = sphere.getRadius();
+        return getSphericalExcess() * radius * radius;
+    }
+
     /**
      * Test if given vector is contained in this triangle with default epsilon 
-1e-9.
      *
@@ -130,4 +162,11 @@ public final class SphericalTriangle {
         return p.copy().add(q).normalize();
     }
 
+    /**
+     * Clamp a value in the [-1 ... 1] range, to avoid NaN results from
+     * {@link Math#acos(double) } caused by floating point rounding errors.
+     */
+    private static double clamp(double value) {
+        return Math.max(-1, Math.min(1, value));
+    }
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/spherical/SphericalTriangleTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/spherical/SphericalTriangleTest.java
index 80cefc7c58..b06e2f5ac7 100644
--- 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/spherical/SphericalTriangleTest.java
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/spherical/SphericalTriangleTest.java
@@ -34,8 +34,6 @@ public class SphericalTriangleTest {
 
     private static final double TOLERANCE = 1e-12;
 
-    private final Sphere sphere = new Sphere(3);
-
     /**
      * Triangle covering the octant of the sphere where x,y,z are all positive.
      * Corners are given in CCW order viewed from outside the sphere.
@@ -43,13 +41,13 @@ public class SphericalTriangleTest {
     private final ReadOnly.Vector<?> a = new Vector3D.Double(1, 0, 0);
     private final ReadOnly.Vector<?> b = new Vector3D.Double(0, 1, 0);
     private final ReadOnly.Vector<?> c = new Vector3D.Double(0, 0, 1);
-    private final SphericalTriangle triangle = new SphericalTriangle(sphere, 
a, b, c);
 
     /**
      * Constructor and corner accessors test.
      */
     @Test
     public void constructorTest() {
+        final SphericalTriangle triangle = new SphericalTriangle(new 
Sphere(3), a, b, c);
         assertSame(a, triangle.getA());
         assertSame(b, triangle.getB());
         assertSame(c, triangle.getC());
@@ -60,19 +58,45 @@ public class SphericalTriangleTest {
      */
     @Test
     public void getCentroidTest() {
+        final SphericalTriangle triangle = new SphericalTriangle(new 
Sphere(3), a, b, c);
         final ReadOnly.Vector<?> centroid = triangle.getCentroid();
         final double v = 1.0 / Math.sqrt(3.0);
         assertArrayEquals(new double[]{v, v, v}, centroid.toArrayDouble(), 
TOLERANCE);
     }
 
     /**
-     * Contains test, using the single argument overload which relies on the
-     * class default epsilon of -1e9. Since this epsilon is far below the
-     * range of possible dot product values for unit vectors, this method
-     * currently returns true regardless of the tested point.
+     * Spherical excess test.
+     * The octant triangle (1,0,0),(0,1,0),(0,0,1) has three right angles,
+     * so its spherical excess is 3*(PI/2) - PI = PI/2.
+     */
+    @Test
+    public void getSphericalExcessTest() {
+        final SphericalTriangle triangle = new SphericalTriangle(new 
Sphere(3), a, b, c);
+        assertEquals(Math.PI / 2, triangle.getSphericalExcess(), TOLERANCE);
+    }
+
+    /**
+     * Area test.
+     * The octant triangle covers 1/8th of the sphere surface (4*PI*r^2),
+     * so its area is PI/2 * r^2.
+     */
+    @Test
+    public void getAreaTest() {
+        final SphericalTriangle triangle = new SphericalTriangle(new 
Sphere(3), a, b, c);
+        assertEquals(Math.PI / 2, triangle.getArea(), TOLERANCE);
+
+        final Sphere biggerSphere = new Sphere(3);
+        biggerSphere.setRadius(2);
+        final SphericalTriangle biggerTriangle = new 
SphericalTriangle(biggerSphere, a, b, c);
+        assertEquals(Math.PI / 2 * 4, biggerTriangle.getArea(), TOLERANCE);
+    }
+
+    /**
+     * Contains test.
      */
     @Test
     public void containsTest() {
+        final SphericalTriangle triangle = new SphericalTriangle(new 
Sphere(3), a, b, c);
         assertTrue(triangle.contains(triangle.getA()));
         assertTrue(triangle.contains(triangle.getB()));
         assertTrue(triangle.contains(triangle.getC()));
@@ -87,6 +111,7 @@ public class SphericalTriangleTest {
      */
     @Test
     public void quadSubdivideTest() {
+        final SphericalTriangle triangle = new SphericalTriangle(new 
Sphere(3), a, b, c);
         final SphericalTriangle[] children = triangle.quadSubdivide();
         assertEquals(4, children.length);
 

Reply via email to