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 7a3f369924 feat(Geometry): add great circle arc
7a3f369924 is described below

commit 7a3f3699240b5dc1986514f9d347b81709dab72a
Author: jsorel <[email protected]>
AuthorDate: Wed Aug 5 16:59:15 2026 +0200

    feat(Geometry): add great circle arc
---
 .../main/org/apache/sis/geometries/Sphere.java     | 18 +++++
 .../sis/geometries/spherical/GreatCircleArc.java   | 79 ++++++++++++++++++++++
 .../geometries/spherical/SphericalTriangle.java    | 43 +++++++-----
 .../geometries/spherical/GreatCircleArcTest.java   | 62 +++++++++++++++++
 .../spherical/SphericalTriangleTest.java           |  8 +--
 5 files changed, 189 insertions(+), 21 deletions(-)

diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Sphere.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Sphere.java
index 585a0bb44c..e480e8998e 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Sphere.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Sphere.java
@@ -59,6 +59,24 @@ public final class Sphere extends AbstractGeometry 
implements ParametricCurveSur
         center = Vectors.createDouble(crs);
     }
 
+    /**
+     * @param dimension number of dimensions of the sphere, must be positive.
+     * @param radius radius new sphere radius, must be positive.
+     */
+    public Sphere(int dimension, double radius) {
+        this(Geometries.getUndefinedCRS(dimension));
+        this.radius = radius;
+    }
+
+    /**
+     * @param crs sphere coordinate system, not null.
+     * @param radius radius new sphere radius, must be positive.
+     */
+    public Sphere(CoordinateReferenceSystem crs, double radius) {
+        center = Vectors.createDouble(crs);
+        this.radius = radius;
+    }
+
     @Override
     public String getGeometryType() {
         return "SPHERE";
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/spherical/GreatCircleArc.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/spherical/GreatCircleArc.java
new file mode 100644
index 0000000000..60140c82a5
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/spherical/GreatCircleArc.java
@@ -0,0 +1,79 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+package org.apache.sis.geometries.spherical;
+
+import org.apache.sis.geometries.Sphere;
+import org.apache.sis.geometries.math.Maths;
+import org.apache.sis.geometries.math.ReadOnly;
+
+/**
+ * A great circle arc on a sphere.
+ *
+ * @see https://mathworld.wolfram.com/GreatCircle.html
+ * @author Johann Sorel (Geomatys)
+ */
+public final class GreatCircleArc {
+
+    /**
+     * base sphere.
+     */
+    private final Sphere sphere;
+    /**
+     * first arc point, as a unit direction vector from the sphere center
+     */
+    private final ReadOnly.Vector<?> vecA;
+    /**
+     * second arc point, as a unit direction vector from the sphere center
+     */
+    private final ReadOnly.Vector<?> vecB;
+
+    public GreatCircleArc(Sphere sphere, ReadOnly.Vector<?> vecA, 
ReadOnly.Vector<?> vecB) {
+        this.sphere = sphere;
+        this.vecA = vecA;
+        this.vecB = vecB;
+    }
+
+    /**
+     * @return the base sphere
+     */
+    public Sphere getSphere() {
+        return sphere;
+    }
+
+    /**
+     * @return first arc point, as a unit direction vector from the sphere 
center
+     */
+    public ReadOnly.Vector<?> getA() {
+        return vecA;
+    }
+
+    /**
+     * @return second arc point, as a unit direction vector from the sphere 
center
+     */
+    public ReadOnly.Vector<?> getB() {
+        return vecB;
+    }
+
+    /**
+     * @return arc length, using the sphere radius
+     */
+    public double getLength() {
+        final double cosAngle = Maths.clamp(vecA.dot(vecB),-1,1);
+        return sphere.getRadius() * Math.acos(cosAngle);
+    }
+
+}
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 3f36848cf5..1dbb4df790 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
@@ -17,6 +17,7 @@
 package org.apache.sis.geometries.spherical;
 
 import org.apache.sis.geometries.Sphere;
+import org.apache.sis.geometries.math.Maths;
 import org.apache.sis.geometries.math.ReadOnly;
 
 /**
@@ -27,9 +28,21 @@ import org.apache.sis.geometries.math.ReadOnly;
  */
 public final class SphericalTriangle {
 
+    /**
+     * base sphere.
+     */
     private final Sphere sphere;
+    /**
+     * first triangle point, as a unit direction vector from the sphere center
+     */
     private final ReadOnly.Vector<?> vecA;
+    /**
+     * second triangle point, as a unit direction vector from the sphere center
+     */
     private final ReadOnly.Vector<?> vecB;
+    /**
+     * third triangle point, as a unit direction vector from the sphere center
+     */
     private final ReadOnly.Vector<?> vecC;
 
     /**
@@ -52,6 +65,13 @@ public final class SphericalTriangle {
         this.vecC = vecC;
     }
 
+    /**
+     * @return the base sphere
+     */
+    public Sphere getSphere() {
+        return sphere;
+    }
+
     /**
      * @return first triangle point, as a unit direction vector from the 
sphere center
      */
@@ -76,7 +96,7 @@ public final class SphericalTriangle {
     /**
      * @return triangle centroid unit direction vector from the sphere center
      */
-    public ReadOnly.Vector<?> getCentroid() {
+    public ReadOnly.Vector<?> getCentroidVector() {
         return vecA.copy().add(vecB).add(vecC).normalize();
     }
 
@@ -88,17 +108,15 @@ public final class SphericalTriangle {
      * @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 cosA = Maths.clamp(vecB.dot(vecC),-1,1);
+        final double cosB = Maths.clamp(vecC.dot(vecA),-1,1);
+        final double cosC = Maths.clamp(vecA.dot(vecB),-1,1);
         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)));
-
+        final double angleA = Math.acos(Maths.clamp((cosA - cosB * cosC) / 
(sinB * sinC),-1,1));
+        final double angleB = Math.acos(Maths.clamp((cosB - cosC * cosA) / 
(sinC * sinA),-1,1));
+        final double angleC = Math.acos(Maths.clamp((cosC - cosA * cosB) / 
(sinA * sinB),-1,1));
         return angleA + angleB + angleC - Math.PI;
     }
 
@@ -162,11 +180,4 @@ 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/GreatCircleArcTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/spherical/GreatCircleArcTest.java
new file mode 100644
index 0000000000..83396fbcbf
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/spherical/GreatCircleArcTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+package org.apache.sis.geometries.spherical;
+
+import org.apache.sis.geometries.Sphere;
+import org.apache.sis.geometries.math.ReadOnly;
+import org.apache.sis.geometries.math.Vector3D;
+
+// Test dependencies
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Tests for {@link GreatCircleArc}.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class GreatCircleArcTest {
+
+    private static final double TOLERANCE = 1e-12;
+
+    private final ReadOnly.Vector<?> a = new Vector3D.Double(1, 0, 0);
+    private final ReadOnly.Vector<?> b = new Vector3D.Double(0, 1, 0);
+
+    /**
+     * Length test.
+     * The arc between (1,0,0) and (0,1,0) spans a quarter of a great circle,
+     * so its length is PI/2 * radius.
+     */
+    @Test
+    public void getLengthTest() {
+        final GreatCircleArc arc = new GreatCircleArc(new Sphere(3), a, b);
+        assertEquals(Math.PI / 2, arc.getLength(), TOLERANCE);
+
+        final GreatCircleArc biggerArc = new GreatCircleArc(new Sphere(3, 
2.0), a, b);
+        assertEquals(Math.PI, biggerArc.getLength(), TOLERANCE);
+
+        // arc between antipodal points spans half a great circle
+        final GreatCircleArc halfArc = new GreatCircleArc(new Sphere(3), a, 
new Vector3D.Double(-1, 0, 0));
+        assertEquals(Math.PI, halfArc.getLength(), TOLERANCE);
+
+        // arc between a point and itself has no length
+        final GreatCircleArc emptyArc = new GreatCircleArc(new Sphere(3), a, 
a);
+        assertEquals(0, emptyArc.getLength(), TOLERANCE);
+    }
+
+}
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 b06e2f5ac7..ee598cbb8a 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
@@ -59,7 +59,7 @@ 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 ReadOnly.Vector<?> centroid = triangle.getCentroidVector();
         final double v = 1.0 / Math.sqrt(3.0);
         assertArrayEquals(new double[]{v, v, v}, centroid.toArrayDouble(), 
TOLERANCE);
     }
@@ -85,9 +85,7 @@ public class SphericalTriangleTest {
         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);
+        final SphericalTriangle biggerTriangle = new SphericalTriangle(new 
Sphere(3, 2), a, b, c);
         assertEquals(Math.PI / 2 * 4, biggerTriangle.getArea(), TOLERANCE);
     }
 
@@ -100,7 +98,7 @@ public class SphericalTriangleTest {
         assertTrue(triangle.contains(triangle.getA()));
         assertTrue(triangle.contains(triangle.getB()));
         assertTrue(triangle.contains(triangle.getC()));
-        assertTrue(triangle.contains(triangle.getCentroid()));
+        assertTrue(triangle.contains(triangle.getCentroidVector()));
         assertFalse(triangle.contains(new Vector3D.Double(-1, 0, 0)));
         assertFalse(triangle.contains(new Vector3D.Double(0, -1, 0)));
         assertFalse(triangle.contains(new Vector3D.Double(0, 0, -1)));

Reply via email to