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