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