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") {