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. + +![ST_ShortestLine](../../../image/ST_ShortestLine/ST_ShortestLine.svg "ST_ShortestLine") + +Format: `ST_ShortestLine(geom1: Geometry, geom2: Geometry)` + +Return type: `Geometry` + +Since: `v1.9.0` + +SQL Example: + +![ST_ShortestLine Point to Point](../../../image/ST_ShortestLine/ST_ShortestLine_point_point.svg "ST_ShortestLine Point to Point") + +```sql +SELECT ST_ShortestLine( + ST_GeomFromText('POINT (0 0)'), + ST_GeomFromText('POINT (3 4)') +) +``` + +Output: + +``` +LINESTRING (0 0, 3 4) +``` + +SQL Example: + +![ST_ShortestLine Point to LineString](../../../image/ST_ShortestLine/ST_ShortestLine_point_linestring.svg "ST_ShortestLine Point to LineString") + +```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. + +![ST_ShortestLine](../../../../image/ST_ShortestLine/ST_ShortestLine.svg "ST_ShortestLine") + +Format: `ST_ShortestLine(geom1: Geometry, geom2: Geometry)` + +Return type: `Geometry` + +SQL Example: + +![ST_ShortestLine Point to Point](../../../../image/ST_ShortestLine/ST_ShortestLine_point_point.svg "ST_ShortestLine Point to Point") + +```sql +SELECT ST_ShortestLine( + ST_GeomFromText('POINT (0 0)'), + ST_GeomFromText('POINT (3 4)') +) +``` + +Output: + +``` +LINESTRING (0 0, 3 4) +``` + +SQL Example: + +![ST_ShortestLine Point to LineString](../../../../image/ST_ShortestLine/ST_ShortestLine_point_linestring.svg "ST_ShortestLine Point to LineString") + +```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. + +![ST_ShortestLine](../../../image/ST_ShortestLine/ST_ShortestLine.svg "ST_ShortestLine") + +Format: `ST_ShortestLine(geom1: Geometry, geom2: Geometry)` + +Return type: `Geometry` + +Since: `v1.9.0` + +SQL Example: + +![ST_ShortestLine Point to Point](../../../image/ST_ShortestLine/ST_ShortestLine_point_point.svg "ST_ShortestLine Point to Point") + +```sql +SELECT ST_ShortestLine( + ST_GeomFromText('POINT (0 0)'), + ST_GeomFromText('POINT (3 4)') +) +``` + +Output: + +``` +LINESTRING (0 0, 3 4) +``` + +SQL Example: + +![ST_ShortestLine Point to LineString](../../../image/ST_ShortestLine/ST_ShortestLine_point_linestring.svg "ST_ShortestLine Point to LineString") + +```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 @@ + + + + + + + + + + + ST_ShortestLine + + + A + + + B + + + + + + + + + Returns the shortest LineString connecting A and B + + + Geometry A + + Geometry B + + Shortest line + 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 @@ + + + + + + + + + + + ST_ShortestLine — Point to LineString + + + + POINT (0 1) + + + + + + + + + + + (0,0) + (1,0) + (2,0) + (3,0) + (4,0) + (5,0) + + + + + + + + + LINESTRING (0 1, 0 0) + + + Point A + + LineString B + + Shortest line + 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 @@ + + + + + + + + + + + ST_ShortestLine — Point to Point + + + + POINT (0 0) + + + + POINT (3 4) + + + + + + + + + LINESTRING (0 0, 3 4) + + + Point A + + Point B + + Shortest line + 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",