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 762e6809ea [GH-2373] Fix ST_Azimuth returning 0.0 instead of null for 
identical points (#2620)
762e6809ea is described below

commit 762e6809ea479faa2163af6ea98de2b8ea4275fe
Author: Jia Yu <[email protected]>
AuthorDate: Sat Feb 7 17:17:19 2026 -0700

    [GH-2373] Fix ST_Azimuth returning 0.0 instead of null for identical points 
(#2620)
---
 .../java/org/apache/sedona/common/Functions.java   |  5 ++++-
 .../org/apache/sedona/common/FunctionsTest.java    | 26 ++++++++++++++++++++++
 docs/api/flink/Function.md                         |  2 +-
 docs/api/snowflake/vector-data/Function.md         |  2 +-
 docs/api/sql/Function.md                           |  2 +-
 python/tests/sql/test_function.py                  | 13 +++++++++--
 .../org/apache/sedona/snowflake/snowsql/UDFs.java  |  2 +-
 .../apache/sedona/snowflake/snowsql/UDFsV2.java    |  2 +-
 .../org/apache/sedona/sql/functionTestScala.scala  | 17 ++++++++++++--
 9 files changed, 61 insertions(+), 10 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java 
b/common/src/main/java/org/apache/sedona/common/Functions.java
index f6805668ce..7af2bc5224 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -204,11 +204,14 @@ public class Functions {
     return Math.sqrt(closest);
   }
 
-  public static double azimuth(Geometry left, Geometry right) {
+  public static Double azimuth(Geometry left, Geometry right) {
     Coordinate leftCoordinate = left.getCoordinate();
     Coordinate rightCoordinate = right.getCoordinate();
     double deltaX = rightCoordinate.x - leftCoordinate.x;
     double deltaY = rightCoordinate.y - leftCoordinate.y;
+    if (deltaX == 0 && deltaY == 0) {
+      return null;
+    }
     double azimuth = Math.atan2(deltaX, deltaY);
     return azimuth < 0 ? azimuth + (2 * Math.PI) : azimuth;
   }
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 19f29cc3bd..94bef0499e 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -3620,6 +3620,32 @@ public class FunctionsTest extends TestBase {
     assertEquals(expected, actual);
   }
 
+  @Test
+  public void azimuthNorth() {
+    Point a = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0));
+    Point b = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 1));
+    Double result = Functions.azimuth(a, b);
+    assertNotNull(result);
+    assertEquals(0.0, result, 1e-9);
+  }
+
+  @Test
+  public void azimuthSouth() {
+    Point a = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 25));
+    Point b = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0));
+    Double result = Functions.azimuth(a, b);
+    assertNotNull(result);
+    assertEquals(Math.PI, result, 1e-9);
+  }
+
+  @Test
+  public void azimuthIdenticalPoints() {
+    Point a = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 25));
+    Point b = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 25));
+    Double result = Functions.azimuth(a, b);
+    assertNull(result);
+  }
+
   @Test
   public void boundsEmptyGeometryReturnsNull() {
     Geometry emptyPoint = GEOMETRY_FACTORY.createPoint();
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 2ea139bc9e..a3f9887757 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -617,7 +617,7 @@ POINT ZM(1 1 1 1)
 
 ## ST_Azimuth
 
-Introduction: Returns Azimuth for two given points in radians null otherwise.
+Introduction: Returns Azimuth for two given points in radians. Returns null if 
the two points are identical.
 
 Format: `ST_Azimuth(pointA: Point, pointB: Point)`
 
diff --git a/docs/api/snowflake/vector-data/Function.md 
b/docs/api/snowflake/vector-data/Function.md
index dbd53e2980..3377d4bfae 100644
--- a/docs/api/snowflake/vector-data/Function.md
+++ b/docs/api/snowflake/vector-data/Function.md
@@ -457,7 +457,7 @@ FROM polygondf
 
 ## ST_Azimuth
 
-Introduction: Returns Azimuth for two given points in radians null otherwise.
+Introduction: Returns Azimuth for two given points in radians. Returns null if 
the two points are identical.
 
 Format: `ST_Azimuth(pointA: Point, pointB: Point)`
 
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index d769d1b851..09967b5aac 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -682,7 +682,7 @@ POINT ZM(1 1 1 1)
 
 ## ST_Azimuth
 
-Introduction: Returns Azimuth for two given points in radians null otherwise.
+Introduction: Returns Azimuth for two given points in radians. Returns null if 
the two points are identical.
 
 Format: `ST_Azimuth(pointA: Point, pointB: Point)`
 
diff --git a/python/tests/sql/test_function.py 
b/python/tests/sql/test_function.py
index 1c35ce417f..14844adb44 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -684,7 +684,11 @@ class TestPredicateJoin(TestBase):
 
     def test_st_azimuth(self):
         sample_points = create_sample_points(20)
-        sample_pair_points = [[el, sample_points[1]] for el in sample_points]
+        sample_pair_points = [
+            [el, sample_points[1]]
+            for el in sample_points
+            if not el.equals(sample_points[1])
+        ]
         schema = StructType(
             [
                 StructField("geomA", GeometryType(), True),
@@ -700,7 +704,6 @@ class TestPredicateJoin(TestBase):
 
         expected_result = [
             240.0133139011053,
-            0.0,
             270.0,
             286.8042682202057,
             315.0,
@@ -723,6 +726,12 @@ class TestPredicateJoin(TestBase):
 
         assert st_azimuth_result == expected_result
 
+        # ST_Azimuth should return null for identical points
+        identical_result = self.spark.sql(
+            "SELECT ST_Azimuth(ST_Point(1.0, 1.0), ST_Point(1.0, 1.0))"
+        ).collect()
+        assert identical_result[0][0] is None
+
         azimuth = self.spark.sql(
             """SELECT ST_Azimuth(ST_Point(25.0, 45.0), ST_Point(75.0, 100.0)) 
AS degA_B,
                       ST_Azimuth(ST_Point(75.0, 100.0), ST_Point(25.0, 45.0)) 
AS degB_A
diff --git 
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java 
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
index ccc5d88bea..c9405df46e 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
@@ -171,7 +171,7 @@ public class UDFs {
   }
 
   @UDFAnnotations.ParamMeta(argNames = {"left", "right"})
-  public static double ST_Azimuth(byte[] left, byte[] right) {
+  public static Double ST_Azimuth(byte[] left, byte[] right) {
     return Functions.azimuth(GeometrySerde.deserialize(left), 
GeometrySerde.deserialize(right));
   }
 
diff --git 
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java 
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
index 0cc7b4e622..09196bae25 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
@@ -228,7 +228,7 @@ public class UDFsV2 {
   @UDFAnnotations.ParamMeta(
       argNames = {"left", "right"},
       argTypes = {"Geometry", "Geometry"})
-  public static double ST_Azimuth(String left, String right) {
+  public static Double ST_Azimuth(String left, String right) {
     return Functions.azimuth(GeometrySerde.deserGeoJson(left), 
GeometrySerde.deserGeoJson(right));
   }
 
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index a095fc1bfa..c2c36113c2 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -1219,15 +1219,17 @@ class functionTestScala
 
     it("Passed ST_Azimuth") {
 
+      val referencePoint = samplePoints.tail.head
       val pointDataFrame = samplePoints
-        .map(point => (point, samplePoints.tail.head))
+        .filterNot(_.equalsExact(referencePoint))
+        .map(point => (point, referencePoint))
         .toDF("geomA", "geomB")
 
       pointDataFrame
         .selectExpr("ST_Azimuth(geomA, geomB)")
         .as[Double]
         .map(180 / math.Pi * _)
-        .collect() should contain theSameElementsAs List(240.0133139011053, 
0.0, 270.0,
+        .collect() should contain theSameElementsAs List(240.0133139011053, 
270.0,
         286.8042682202057, 315.0, 314.9543472191815, 315.0058223408927, 
245.14762725688198,
         314.84984546897755, 314.8868529256147, 314.9510567053395, 
314.95443984912936,
         314.89925480835245, 314.6018799143881, 314.6834083423315, 
314.80689827870725,
@@ -1250,6 +1252,17 @@ class functionTestScala
         .collect()
         .toList should contain theSameElementsAs List(42.27368900609374, 
222.27368900609375,
         270.00, 90.0, 180.0, 0.0)
+
+      // ST_Azimuth should return null for identical points
+      val identicalPoints = Seq(("POINT(1.0 1.0)", "POINT(1.0 1.0)"))
+        .map({ case (wktA, wktB) => (wktReader.read(wktA), 
wktReader.read(wktB)) })
+        .toDF("geomA", "geomB")
+
+      identicalPoints
+        .selectExpr("ST_Azimuth(geomA, geomB)")
+        .collect()
+        .head
+        .isNullAt(0) shouldBe true
     }
 
     it("Should pass ST_X") {

Reply via email to