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 0c7f487b658..fdec8913c37 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -1115,6 +1115,15 @@ public static Geometry closestPoint(Geometry left, Geometry right) {
}
}
+ public static Geometry shortestLine(Geometry left, Geometry right) {
+ if (left.isEmpty() || right.isEmpty()) {
+ return null;
+ }
+ DistanceOp distanceOp = new DistanceOp(left, right);
+ Coordinate[] closestPoints = distanceOp.nearestPoints();
+ return left.getFactory().createLineString(closestPoints);
+ }
+
public static Geometry delaunayTriangle(Geometry geometry) {
return delaunayTriangle(geometry, 0.0, 0);
}
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 882a18de82d..de82c2d84bb 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -4192,6 +4192,61 @@ public void closestPointEmpty() {
assertEquals(expected, e2.getMessage());
}
+ @Test
+ public void shortestLinePointToPoint() {
+ Point point1 = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0));
+ Point point2 = GEOMETRY_FACTORY.createPoint(new Coordinate(3, 4));
+ String expected = "LINESTRING (0 0, 3 4)";
+ String actual = Functions.shortestLine(point1, point2).toText();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void shortestLinePointToLineString() {
+ Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(160, 40));
+ LineString lineString =
+ GEOMETRY_FACTORY.createLineString(
+ coordArray(10, 30, 50, 50, 30, 110, 70, 90, 180, 140, 130, 190));
+ Geometry result = Functions.shortestLine(point, lineString);
+ assertNotNull(result);
+ assertEquals("LineString", result.getGeometryType());
+ // First point should be on the point geometry, second on the linestring
+ assertEquals(160.0, result.getCoordinates()[0].x, 1e-6);
+ assertEquals(40.0, result.getCoordinates()[0].y, 1e-6);
+ }
+
+ @Test
+ public void shortestLinePolygonToPolygon() {
+ Polygon polygonA =
+ GEOMETRY_FACTORY.createPolygon(coordArray(190, 150, 20, 10, 160, 70, 190, 150));
+ Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(80, 160));
+ Geometry polygonB = Functions.buffer(point, 30);
+ Geometry result = Functions.shortestLine(polygonA, polygonB);
+ assertNotNull(result);
+ assertEquals("LineString", result.getGeometryType());
+ // The length of the shortest line should equal the distance between the polygons
+ assertEquals(Functions.distance(polygonA, polygonB), result.getLength(), 1e-6);
+ }
+
+ @Test
+ public void shortestLineEmptyGeometry() {
+ Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1));
+ LineString emptyLineString = GEOMETRY_FACTORY.createLineString();
+ assertNull(Functions.shortestLine(point, emptyLineString));
+
+ Polygon emptyPolygon = GEOMETRY_FACTORY.createPolygon();
+ assertNull(Functions.shortestLine(emptyPolygon, emptyLineString));
+ }
+
+ @Test
+ public void shortestLineSameGeometry() {
+ Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(5, 5));
+ Geometry result = Functions.shortestLine(point, point);
+ assertNotNull(result);
+ assertEquals("LINESTRING (5 5, 5 5)", result.toText());
+ assertEquals(0.0, result.getLength(), 1e-6);
+ }
+
@Test
public void testZmFlag() throws ParseException {
int _2D = 0, _3DM = 1, _3DZ = 2, _4D = 3;
diff --git a/docs/api/flink/Geometry-Functions.md b/docs/api/flink/Geometry-Functions.md
index a5f31304b52..d0c3f42d5c9 100644
--- a/docs/api/flink/Geometry-Functions.md
+++ b/docs/api/flink/Geometry-Functions.md
@@ -201,6 +201,7 @@ These functions compute measurements of distance, area, length, and angles.
| [ST_MinimumClearanceLine](Measurement-Functions/ST_MinimumClearanceLine.md) | Geometry | This function returns a two-point LineString geometry representing the minimum clearance distance of the input geometry. If the input geometry does not have a defined minimum clearance, such as for... | v1.6.1 |
| [ST_Perimeter](Measurement-Functions/ST_Perimeter.md) | Double | This function calculates the 2D perimeter of a given geometry. It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long as the GeometryCollection contains polygonal geometries)... | v1.7.0 |
| [ST_Perimeter2D](Measurement-Functions/ST_Perimeter2D.md) | Double | This function calculates the 2D perimeter of a given geometry. It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long as the GeometryCollection contains polygonal geometries)... | v1.7.1 |
+| [ST_ShortestLine](Measurement-Functions/ST_ShortestLine.md) | Geometry | Returns the shortest LineString between two geometries. The line starts on geom1 and ends on geom2. If either geometry is empty, returns null. | v1.9.0 |
## Geometry Processing
diff --git a/docs/api/flink/Measurement-Functions/ST_ShortestLine.md b/docs/api/flink/Measurement-Functions/ST_ShortestLine.md
new file mode 100644
index 00000000000..04631551c0a
--- /dev/null
+++ b/docs/api/flink/Measurement-Functions/ST_ShortestLine.md
@@ -0,0 +1,64 @@
+
+
+# ST_ShortestLine
+
+Introduction: Returns the shortest LineString between two geometries. The line starts on geom1 and ends on geom2. If either geometry is empty, the function returns null.
+
+
+
+Format: `ST_ShortestLine(geom1: Geometry, geom2: Geometry)`
+
+Return type: `Geometry`
+
+Since: `v1.9.0`
+
+SQL Example:
+
+
+
+```sql
+SELECT ST_ShortestLine(
+ ST_GeomFromText('POINT (0 0)'),
+ ST_GeomFromText('POINT (3 4)')
+)
+```
+
+Output:
+
+```
+LINESTRING (0 0, 3 4)
+```
+
+SQL Example:
+
+
+
+```sql
+SELECT ST_ShortestLine(
+ ST_GeomFromText('POINT (0 1)'),
+ ST_GeomFromText('LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)')
+)
+```
+
+Output:
+
+```
+LINESTRING (0 1, 0 0)
+```
diff --git a/docs/api/snowflake/vector-data/Geometry-Functions.md b/docs/api/snowflake/vector-data/Geometry-Functions.md
index f51f00f2d82..e22c5d0a308 100644
--- a/docs/api/snowflake/vector-data/Geometry-Functions.md
+++ b/docs/api/snowflake/vector-data/Geometry-Functions.md
@@ -193,6 +193,7 @@ These functions compute measurements of distance, area, length, and angles.
| [ST_MinimumClearanceLine](Measurement-Functions/ST_MinimumClearanceLine.md) | This function returns a two-point LineString geometry representing the minimum clearance distance of the input geometry. If the input geometry does not have a defined minimum clearance, such as for... |
| [ST_Perimeter](Measurement-Functions/ST_Perimeter.md) | This function calculates the 2D perimeter of a given geometry. It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long as the GeometryCollection contains polygonal geometries)... |
| [ST_Perimeter2D](Measurement-Functions/ST_Perimeter2D.md) | This function calculates the 2D perimeter of a given geometry. It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long as the GeometryCollection contains polygonal geometries)... |
+| [ST_ShortestLine](Measurement-Functions/ST_ShortestLine.md) | Returns the shortest LineString between two geometries. The line starts on geom1 and ends on geom2. If either geometry is empty, returns null. |
## Geometry Processing
diff --git a/docs/api/snowflake/vector-data/Measurement-Functions/ST_ShortestLine.md b/docs/api/snowflake/vector-data/Measurement-Functions/ST_ShortestLine.md
new file mode 100644
index 00000000000..ae6b369aadb
--- /dev/null
+++ b/docs/api/snowflake/vector-data/Measurement-Functions/ST_ShortestLine.md
@@ -0,0 +1,62 @@
+
+
+# ST_ShortestLine
+
+Introduction: Returns the shortest LineString between two geometries. The line starts on geom1 and ends on geom2. If either geometry is empty, the function returns null.
+
+
+
+Format: `ST_ShortestLine(geom1: Geometry, geom2: Geometry)`
+
+Return type: `Geometry`
+
+SQL Example:
+
+
+
+```sql
+SELECT ST_ShortestLine(
+ ST_GeomFromText('POINT (0 0)'),
+ ST_GeomFromText('POINT (3 4)')
+)
+```
+
+Output:
+
+```
+LINESTRING (0 0, 3 4)
+```
+
+SQL Example:
+
+
+
+```sql
+SELECT ST_ShortestLine(
+ ST_GeomFromText('POINT (0 1)'),
+ ST_GeomFromText('LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)')
+)
+```
+
+Output:
+
+```
+LINESTRING (0 1, 0 0)
+```
diff --git a/docs/api/sql/Geometry-Functions.md b/docs/api/sql/Geometry-Functions.md
index a5076679ffa..14ce382530b 100644
--- a/docs/api/sql/Geometry-Functions.md
+++ b/docs/api/sql/Geometry-Functions.md
@@ -202,6 +202,7 @@ These functions compute measurements of distance, area, length, and angles.
| [ST_MinimumClearanceLine](Measurement-Functions/ST_MinimumClearanceLine.md) | Geometry | This function returns a two-point LineString geometry representing the minimum clearance distance of the input geometry. If the input geometry does not have a defined minimum clearance, such as for... | v1.6.1 |
| [ST_Perimeter](Measurement-Functions/ST_Perimeter.md) | Double | This function calculates the 2D perimeter of a given geometry. It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long as the GeometryCollection contains polygonal geometries)... | v1.7.0 |
| [ST_Perimeter2D](Measurement-Functions/ST_Perimeter2D.md) | Double | This function calculates the 2D perimeter of a given geometry. It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long as the GeometryCollection contains polygonal geometries)... | v1.7.1 |
+| [ST_ShortestLine](Measurement-Functions/ST_ShortestLine.md) | Geometry | Returns the shortest LineString between two geometries. The line starts on geom1 and ends on geom2. If either geometry is empty, returns null. | v1.9.0 |
## Geometry Processing
diff --git a/docs/api/sql/Measurement-Functions/ST_ShortestLine.md b/docs/api/sql/Measurement-Functions/ST_ShortestLine.md
new file mode 100644
index 00000000000..04631551c0a
--- /dev/null
+++ b/docs/api/sql/Measurement-Functions/ST_ShortestLine.md
@@ -0,0 +1,64 @@
+
+
+# ST_ShortestLine
+
+Introduction: Returns the shortest LineString between two geometries. The line starts on geom1 and ends on geom2. If either geometry is empty, the function returns null.
+
+
+
+Format: `ST_ShortestLine(geom1: Geometry, geom2: Geometry)`
+
+Return type: `Geometry`
+
+Since: `v1.9.0`
+
+SQL Example:
+
+
+
+```sql
+SELECT ST_ShortestLine(
+ ST_GeomFromText('POINT (0 0)'),
+ ST_GeomFromText('POINT (3 4)')
+)
+```
+
+Output:
+
+```
+LINESTRING (0 0, 3 4)
+```
+
+SQL Example:
+
+
+
+```sql
+SELECT ST_ShortestLine(
+ ST_GeomFromText('POINT (0 1)'),
+ ST_GeomFromText('LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)')
+)
+```
+
+Output:
+
+```
+LINESTRING (0 1, 0 0)
+```
diff --git a/docs/image/ST_ShortestLine/ST_ShortestLine.svg b/docs/image/ST_ShortestLine/ST_ShortestLine.svg
new file mode 100644
index 00000000000..e7294fce25b
--- /dev/null
+++ b/docs/image/ST_ShortestLine/ST_ShortestLine.svg
@@ -0,0 +1,36 @@
+
diff --git a/docs/image/ST_ShortestLine/ST_ShortestLine_point_linestring.svg b/docs/image/ST_ShortestLine/ST_ShortestLine_point_linestring.svg
new file mode 100644
index 00000000000..e8b9eb10c30
--- /dev/null
+++ b/docs/image/ST_ShortestLine/ST_ShortestLine_point_linestring.svg
@@ -0,0 +1,50 @@
+
diff --git a/docs/image/ST_ShortestLine/ST_ShortestLine_point_point.svg b/docs/image/ST_ShortestLine/ST_ShortestLine_point_point.svg
new file mode 100644
index 00000000000..bbef32af64f
--- /dev/null
+++ b/docs/image/ST_ShortestLine/ST_ShortestLine_point_point.svg
@@ -0,0 +1,38 @@
+
diff --git a/flink/src/main/java/org/apache/sedona/flink/Catalog.java b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
index 2d3646c43cc..a10d9f15777 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -69,6 +69,7 @@ public static UserDefinedFunction[] getFuncs() {
new Functions.ST_Buffer(),
new Functions.ST_BestSRID(),
new Functions.ST_ClosestPoint(),
+ new Functions.ST_ShortestLine(),
new Functions.ST_Centroid(),
new Functions.ST_Collect(),
new Functions.ST_CollectionExtract(),
diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
index 635cf86a2d3..cf5e5267c78 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
@@ -287,6 +287,28 @@ public Geometry eval(
}
}
+ public static class ST_ShortestLine extends ScalarFunction {
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = GeometryTypeSerializer.class,
+ bridgedTo = Geometry.class)
+ public Geometry eval(
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = GeometryTypeSerializer.class,
+ bridgedTo = Geometry.class)
+ Object g1,
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = GeometryTypeSerializer.class,
+ bridgedTo = Geometry.class)
+ Object g2) {
+ Geometry geom1 = (Geometry) g1;
+ Geometry geom2 = (Geometry) g2;
+ return org.apache.sedona.common.Functions.shortestLine(geom1, geom2);
+ }
+ }
+
public static class ST_Centroid extends ScalarFunction {
@DataTypeHint(
value = "RAW",
diff --git a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
index a348c074d73..93ca5e5f0c0 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -238,6 +238,16 @@ public void testClosestPoint() {
assertEquals("POINT (160 40)", result.toString());
}
+ @Test
+ public void testShortestLine() {
+ Table table =
+ tableEnv.sqlQuery(
+ "SELECT ST_GeomFromWKT('POINT (0 0)') AS g1, ST_GeomFromWKT('POINT (3 4)') as g2");
+ table = table.select(call(Functions.ST_ShortestLine.class.getSimpleName(), $("g1"), $("g2")));
+ Geometry result = (Geometry) first(table).getField(0);
+ assertEquals("LINESTRING (0 0, 3 4)", result.toString());
+ }
+
@Test
public void testCentroid() {
Table polygonTable =
diff --git a/python/sedona/spark/sql/st_functions.py b/python/sedona/spark/sql/st_functions.py
index 0a52d8c4c28..f32d57e7adc 100644
--- a/python/sedona/spark/sql/st_functions.py
+++ b/python/sedona/spark/sql/st_functions.py
@@ -471,6 +471,20 @@ def ST_ClosestPoint(a: ColumnOrName, b: ColumnOrName) -> Column:
return _call_st_function("ST_ClosestPoint", (a, b))
+@validate_argument_types
+def ST_ShortestLine(a: ColumnOrName, b: ColumnOrName) -> Column:
+ """Return the shortest line between two geometries.
+
+ :param a: One geometry column.
+ :type a: ColumnOrName
+ :param b: Other geometry column.
+ :type b: ColumnOrName
+ :return: Shortest LineString connecting the two geometries as a geometry column.
+ :rtype: Column
+ """
+ return _call_st_function("ST_ShortestLine", (a, b))
+
+
@validate_argument_types
def ST_ConcaveHull(
geometry: ColumnOrName,
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
index 81dcd5055a9..920550a1cfe 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -444,6 +444,16 @@
"",
"POINT (0 1)",
),
+ (
+ stf.ST_ShortestLine,
+ (
+ "point",
+ "line",
+ ),
+ "point_and_line",
+ "",
+ "LINESTRING (0 1, 0 0)",
+ ),
(
stf.ST_CollectionExtract,
("geom",),
diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py
index 031978032d6..48a24d0ce7a 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -1966,6 +1966,23 @@ def test_st_closest_point(self):
actual = actual_df.take(1)[0][0]
assert expected == actual
+ def test_st_shortest_line(self):
+ expected = "LINESTRING (0 1, 0 0)"
+ actual_df = self.spark.sql(
+ "select ST_AsText(ST_ShortestLine(ST_GeomFromText('POINT (0 1)'), "
+ "ST_GeomFromText('LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)')))"
+ )
+ actual = actual_df.take(1)[0][0]
+ assert expected == actual
+
+ def test_st_shortest_line_empty(self):
+ actual_df = self.spark.sql(
+ "select ST_ShortestLine(ST_GeomFromText('POINT (0 1)'), "
+ "ST_GeomFromText('GEOMETRYCOLLECTION EMPTY'))"
+ )
+ actual = actual_df.take(1)[0][0]
+ assert actual is None
+
def test_st_collect_on_array_type(self):
# given
geometry_df = self.spark.createDataFrame(
diff --git a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 815d2c6eb26..c931694db40 100644
--- a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -136,6 +136,7 @@ object Catalog extends AbstractCatalog with Logging {
function[ST_StartPoint](),
function[ST_Snap](),
function[ST_ClosestPoint](),
+ function[ST_ShortestLine](),
function[ST_Boundary](),
function[ST_HasZ](),
function[ST_HasM](),
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 0fab6060ccf..0925dfb1043 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -1004,6 +1004,14 @@ private[apache] case class ST_ClosestPoint(inputExpressions: Seq[Expression])
}
}
+private[apache] case class ST_ShortestLine(inputExpressions: Seq[Expression])
+ extends InferredExpression(Functions.shortestLine _) {
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+ copy(inputExpressions = newChildren)
+ }
+}
+
private[apache] case class ST_IsPolygonCW(inputExpressions: Seq[Expression])
extends InferredExpression(Functions.isPolygonCW _) {
protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
index f79fc78be53..07f2e78d3bf 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -156,6 +156,9 @@ object st_functions {
def ST_ClosestPoint(a: Column, b: Column): Column = wrapExpression[ST_ClosestPoint](a, b)
def ST_ClosestPoint(a: String, b: String): Column = wrapExpression[ST_ClosestPoint](a, b)
+ def ST_ShortestLine(a: Column, b: Column): Column = wrapExpression[ST_ShortestLine](a, b)
+ def ST_ShortestLine(a: String, b: String): Column = wrapExpression[ST_ShortestLine](a, b)
+
def ST_Collect(geoms: Column): Column = wrapExpression[ST_Collect](geoms)
def ST_Collect(geoms: String): Column = wrapExpression[ST_Collect](geoms)
def ST_Collect(geoms: Any*): Column = wrapVarArgExpression[ST_Collect](geoms)
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala b/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala
index 749a1be48a2..b9e5b7b29e5 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala
@@ -74,6 +74,7 @@ class PreserveSRIDSuite extends TestBaseScala with TableDrivenPropertyChecks {
("ST_RemoveRepeatedPoints(geom3, 1)", 1000),
("ST_SetPoint(geom3, 1, ST_Point(0.5, 0.5))", 1000),
("ST_ClosestPoint(geom1, geom2)", 1000),
+ ("ST_ShortestLine(geom1, geom2)", 1000),
("ST_FlipCoordinates(geom1)", 1000),
("ST_SubDivide(geom4, 5)", 1000),
("ST_Segmentize(geom4, 0.1)", 1000),
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
index d31da962dcd..f10f36e38af 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -1619,6 +1619,14 @@ class dataFrameAPITestScala extends TestBaseScala {
assertEquals(expected, actual)
}
+ it("Passed ST_ShortestLine") {
+ val polyDf = sparkSession.sql(
+ "SELECT ST_GeomFromWKT('POINT (0 1)') as g1, ST_GeomFromWKT('LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)') as g2")
+ val df = polyDf.select(ST_ShortestLine("g1", "g2"))
+ val actual = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+ assertEquals("LINESTRING (0 1, 0 0)", actual)
+ }
+
it("Passed ST_AsEWKT") {
val baseDf = sparkSession.sql("SELECT ST_SetSRID(ST_Point(0.0, 0.0), 4326) AS point")
val df = baseDf.select(ST_AsEWKT("point"))
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 377e598b48d..8d311bae90f 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
@@ -3023,6 +3023,30 @@ class functionTestScala
}
}
+ it("should pass ST_ShortestLine") {
+ // Point to point
+ var df =
+ sparkSession.sql(
+ "SELECT ST_ShortestLine(ST_GeomFromWKT('POINT (0 0)'), ST_GeomFromWKT('POINT (3 4)'))")
+ var actual = df.take(1)(0).get(0).asInstanceOf[Geometry].toText
+ assertEquals("LINESTRING (0 0, 3 4)", actual)
+
+ // Point to linestring — first coordinate should be the point
+ df = sparkSession.sql(
+ "SELECT ST_ShortestLine(ST_GeomFromWKT('POINT (160 40)'), ST_GeomFromWKT('LINESTRING (10 30, 50 50, 30 110, 70 90, 180 140, 130 190)'))")
+ val result = df.take(1)(0).get(0).asInstanceOf[Geometry]
+ assertEquals("LineString", result.getGeometryType)
+ assertEquals(160.0, result.getCoordinates()(0).x, 1e-6)
+ assertEquals(40.0, result.getCoordinates()(0).y, 1e-6)
+ }
+
+ it("should return null for ST_ShortestLine with empty geometry") {
+ val df = sparkSession.sql(
+ "SELECT ST_ShortestLine(ST_GeomFromWKT('POINT (0 0)'), ST_GeomFromWKT('GEOMETRYCOLLECTION EMPTY'))")
+ val result = df.take(1)(0).get(0)
+ assert(result == null)
+ }
+
it("Should pass ST_AreaSpheroid") {
val geomTestCases = Map(
("'POINT (-0.56 51.3168)'") -> "0.0",