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 8a1c4046 [SEDONA-298] Implement ST_ClosestPoint (#877)
8a1c4046 is described below

commit 8a1c404612e710badede5264811ce955b420799e
Author: Junhao Liu <[email protected]>
AuthorDate: Sun Jul 2 13:24:05 2023 +0800

    [SEDONA-298] Implement ST_ClosestPoint (#877)
---
 .../java/org/apache/sedona/common/Functions.java   | 12 ++++
 .../org/apache/sedona/common/FunctionsTest.java    | 72 +++++++++++++++++++++-
 docs/api/flink/Function.md                         | 26 ++++++++
 docs/api/sql/Function.md                           | 26 ++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |  1 +
 .../apache/sedona/flink/expressions/Functions.java | 10 +++
 .../java/org/apache/sedona/flink/FunctionTest.java |  6 ++
 python/sedona/sql/st_functions.py                  | 16 +++++
 python/tests/sql/test_dataframe_api.py             |  1 +
 python/tests/sql/test_function.py                  |  7 +++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../sql/sedona_sql/expressions/Functions.scala     |  8 +++
 .../sql/sedona_sql/expressions/st_functions.scala  |  3 +
 .../apache/sedona/sql/dataFrameAPITestScala.scala  |  8 +++
 .../org/apache/sedona/sql/functionTestScala.scala  | 16 +++++
 15 files changed, 211 insertions(+), 2 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 2bed5fbc..ee9031cc 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -30,6 +30,7 @@ import org.locationtech.jts.geom.util.GeometryFixer;
 import org.locationtech.jts.io.gml2.GMLWriter;
 import org.locationtech.jts.io.kml.KMLWriter;
 import org.locationtech.jts.linearref.LengthIndexedLine;
+import org.locationtech.jts.operation.distance.DistanceOp;
 import org.locationtech.jts.operation.distance3d.Distance3DOp;
 import org.locationtech.jts.operation.linemerge.LineMerger;
 import org.locationtech.jts.operation.valid.IsSimpleOp;
@@ -447,6 +448,17 @@ public class Functions {
         return GEOMETRY_FACTORY.createLineString(coordinates.toArray(new 
Coordinate[0]));
     }
 
+    public static Geometry closestPoint(Geometry left, Geometry right) {
+        DistanceOp distanceOp = new DistanceOp(left, right);
+        try {
+            Coordinate[] closestPoints = distanceOp.nearestPoints();
+            return GEOMETRY_FACTORY.createPoint(closestPoints[0]);
+        }
+        catch (Exception e) {
+            throw new IllegalArgumentException("ST_ClosestPoint doesn't 
support empty geometry object.");
+        }
+    }
+
     public static Geometry concaveHull(Geometry geometry, double pctConvex, 
boolean allowHoles){
         ConcaveHull concave_hull = new ConcaveHull(geometry);
         concave_hull.setMaximumEdgeLengthRatio(pctConvex);
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 46db35e7..2a238fcd 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -30,7 +30,6 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import static org.junit.Assert.*;
-import static org.junit.Assert.assertEquals;
 
 public class FunctionsTest {
     public static final GeometryFactory GEOMETRY_FACTORY = new 
GeometryFactory();
@@ -310,7 +309,7 @@ public class FunctionsTest {
         Integer expectedResult = 0;
         assertEquals(actualResult, expectedResult);
 
-        LineString lineString3D = 
GEOMETRY_FACTORY.createLineString(coordArray(1, 0, 1, 1, 1, 2));
+        LineString lineString3D = 
GEOMETRY_FACTORY.createLineString(coordArray3d(1, 0, 1, 1, 1, 2));
         actualResult = Functions.dimension(lineString3D);
         expectedResult = 1;
         assertEquals(actualResult, expectedResult);
@@ -1307,6 +1306,75 @@ public class FunctionsTest {
         assertEquals(expected4, actual4);
     }
 
+    @Test
+    public void closestPoint() {
+        Point point1 = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1));
+        LineString lineString1 = 
GEOMETRY_FACTORY.createLineString(coordArray(1, 0, 1, 1, 2, 1, 2, 0, 1, 0));
+        String expected1 = "POINT (1 1)";
+        String actual1 = Functions.closestPoint(point1, lineString1).toText();
+        assertEquals(expected1, actual1);
+
+        Point point2 = GEOMETRY_FACTORY.createPoint(new Coordinate(160, 40));
+        LineString lineString2 = 
GEOMETRY_FACTORY.createLineString(coordArray(10, 30, 50, 50, 30, 110, 70, 90, 
180, 140, 130, 190));
+        String expected2 = "POINT (160 40)";
+        String actual2 = Functions.closestPoint(point2, lineString2).toText();
+        assertEquals(expected2, actual2);
+        Point expectedPoint3 = GEOMETRY_FACTORY.createPoint(new 
Coordinate(125.75342465753425, 115.34246575342466));
+        Double expected3 = Functions.closestPoint(lineString2, 
point2).distance(expectedPoint3);
+        assertEquals(expected3, 0, 1e-6);
+
+        Point point4 = GEOMETRY_FACTORY.createPoint(new Coordinate(80, 160));
+        Polygon polygonA = GEOMETRY_FACTORY.createPolygon(coordArray(190, 150, 
20, 10, 160, 70, 190, 150));
+        Geometry polygonB = Functions.buffer(point4, 30);
+        Point expectedPoint4 = GEOMETRY_FACTORY.createPoint(new 
Coordinate(131.59149149528952, 101.89887534906197));
+        Double expected4 = Functions.closestPoint(polygonA, 
polygonB).distance(expectedPoint4);
+        assertEquals(expected4, 0, 1e-6);
+    }
+
+    @Test
+    public void closestPoint3d() {
+        // One of the object is 3D
+        Point point1 = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 
1,10000));
+        LineString lineString1 = 
GEOMETRY_FACTORY.createLineString(coordArray(1, 0, 1, 1, 2, 1, 2, 0, 1, 0));
+        String expected1 = "POINT (1 1)";
+        String actual1 = Functions.closestPoint(point1, lineString1).toText();
+        assertEquals(expected1, actual1);
+
+        // Both of the object are 3D
+        LineString lineString3D = 
GEOMETRY_FACTORY.createLineString(coordArray3d(1, 0, 100, 1, 1, 20, 2, 1, 40, 
2, 0, 60, 1, 0, 70));
+        String expected2 = "POINT (1 1)";
+        String actual2 = Functions.closestPoint(point1, lineString3D).toText();
+        assertEquals(expected2, actual2);
+    }
+
+    @Test
+    public void closestPointGeomtryCollection() {
+        LineString line = GEOMETRY_FACTORY.createLineString(coordArray(2, 0, 
0, 2));
+        Geometry[] geometry = new Geometry[] {
+                GEOMETRY_FACTORY.createLineString(coordArray(2, 0, 2, 1)),
+                GEOMETRY_FACTORY.createPolygon(coordArray(0.0, 0.0, 1.0, 1.0, 
1.0, 0.0, 0.0, 0.0))
+        };
+        GeometryCollection geometryCollection = 
GEOMETRY_FACTORY.createGeometryCollection(geometry);
+        String actual1 = Functions.closestPoint(line, 
geometryCollection).toText();
+        String expected1 = "POINT (2 0)";
+        assertEquals(actual1 ,expected1);
+    }
+
+    @Test
+    public void closestPointEmpty() {
+        // One of the object is empty
+        Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1));
+        LineString emptyLineString = GEOMETRY_FACTORY.createLineString();
+        String expected = "ST_ClosestPoint doesn't support empty geometry 
object.";
+        Exception e1 = assertThrows(IllegalArgumentException.class, () -> 
Functions.closestPoint(point, emptyLineString));
+        assertEquals(expected, e1.getMessage());
+        
+        // Both objects are empty
+        Polygon emptyPolygon = GEOMETRY_FACTORY.createPolygon();
+        Exception e2 = assertThrows(IllegalArgumentException.class, () -> 
Functions.closestPoint(emptyPolygon, emptyLineString));
+        assertEquals(expected, e2.getMessage());
+    }
+
     @Test
     public void hausdorffDistanceDefaultGeom2D() throws Exception {
         Polygon polygon1 = GEOMETRY_FACTORY.createPolygon(coordArray3d(1, 0, 
1, 1, 1, 2, 2, 1, 5, 2, 0, 1, 1, 0, 1));
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 2d77a8ce..5bff4674 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -445,6 +445,32 @@ SELECT ST_Centroid(polygondf.countyshape)
 FROM polygondf
 ```
 
+## ST_ClosestPoint
+
+Introduction: Returns the 2-dimensional point on geom1 that is closest to 
geom2. This is the first point of the shortest line between the geometries. If 
using 3D geometries, the Z coordinates will be ignored. If you have a 3D 
Geometry, you may prefer to use ST_3DClosestPoint.
+It will throw an exception indicates illegal argument if one of the params is 
an empty geometry.
+
+Format: `ST_ClosestPoint(g1: geomtry, g2: geometry)`
+
+Since: `1.5.0`
+
+Example1:
+```sql
+SELECT ST_AsText( ST_ClosestPoint(g1, g2)) As ptwkt;
+```
+
+Input: `g1: POINT (160 40), g2: LINESTRING (10 30, 50 50, 30 110, 70 90, 180 
140, 130 190)`
+
+Output: `POINT(160 40)`
+
+Input: `g1: LINESTRING (10 30, 50 50, 30 110, 70 90, 180 140, 130 190), g2: 
POINT (160 40)`
+
+Output: `POINT(125.75342465753425 115.34246575342466)`
+
+Input: `g1: 'POLYGON ((190 150, 20 10, 160 70, 190 150))', g2: 
ST_Buffer('POINT(80 160)', 30)`
+
+Output: `POINT(131.59149149528952 101.89887534906197)`
+
 ## ST_CollectionExtract
 
 Introduction: Returns a homogeneous multi-geometry from a given geometry 
collection.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 698f3425..21821872 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -445,6 +445,32 @@ SELECT ST_Centroid(polygondf.countyshape)
 FROM polygondf
 ```
 
+## ST_ClosestPoint
+
+Introduction: Returns the 2-dimensional point on geom1 that is closest to 
geom2. This is the first point of the shortest line between the geometries. If 
using 3D geometries, the Z coordinates will be ignored. If you have a 3D 
Geometry, you may prefer to use ST_3DClosestPoint.
+It will throw an exception indicates illegal argument if one of the params is 
an empty geometry.
+
+Format: `ST_ClosestPoint(g1: geomtry, g2: geometry)`
+
+Since: `1.5.0`
+
+Example1:
+```sql
+SELECT ST_AsText( ST_ClosestPoint(g1, g2)) As ptwkt;
+```
+
+Input: `g1: POINT (160 40), g2: LINESTRING (10 30, 50 50, 30 110, 70 90, 180 
140, 130 190)`
+
+Output: `POINT(160 40)`
+
+Input: `g1: LINESTRING (10 30, 50 50, 30 110, 70 90, 180 140, 130 190), g2: 
POINT (160 40)`
+
+Output: `POINT(125.75342465753425 115.34246575342466)`
+
+Input: `g1: 'POLYGON ((190 150, 20 10, 160 70, 190 150))', g2: 
ST_Buffer('POINT(80 160)', 30)`
+
+Output: `POINT(131.59149149528952 101.89887534906197)`
+
 ## ST_Collect
 
 Introduction: Returns MultiGeometry object based on geometry column/s or array 
with geometries
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 50c9bf74..1df1fb44 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -44,6 +44,7 @@ public class Catalog {
                 new Functions.ST_Azimuth(),
                 new Functions.ST_Boundary(),
                 new Functions.ST_Buffer(),
+                new Functions.ST_ClosestPoint(),
                 new Functions.ST_Centroid(),
                 new Functions.ST_CollectionExtract(),
                 new Functions.ST_ConcaveHull(),
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 a3551a3a..56b1af76 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
@@ -71,6 +71,16 @@ public class Functions {
         }
     }
 
+    public static class ST_ClosestPoint extends ScalarFunction {
+        @DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class)
+        public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class) Object g1,
+        @DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class) Object g2) {
+            Geometry geom1 = (Geometry) g1;
+            Geometry geom2 = (Geometry) g2;
+            return org.apache.sedona.common.Functions.closestPoint(geom1, 
geom2);
+        }
+    }
+    
     public static class ST_Centroid extends ScalarFunction {
         @DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class) Object o) {
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 5c08c124..42fd9772 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -85,6 +85,12 @@ public class FunctionTest extends TestBase{
     }
 
     @Test
+    public void testClosestPoint() {
+        Table table = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POINT (160 
40)') AS g1, ST_GeomFromWKT('POINT (10 10)') as g2");
+        table = 
table.select(call(Functions.ST_ClosestPoint.class.getSimpleName(), $("g1"), 
$("g2")));
+        Geometry result = (Geometry) first(table).getField(0);
+        assertEquals("POINT (160 40)", result.toString());
+    }   
     public void testCentroid() {
         Table polygonTable = tableEnv.sqlQuery("SELECT 
ST_GeomFromText('POLYGON ((2 2, 0 0, 2 0, 0 2, 2 2))') as geom");
         Table resultTable = 
polygonTable.select(call(Functions.ST_Centroid.class.getSimpleName(), 
$("geom")));
diff --git a/python/sedona/sql/st_functions.py 
b/python/sedona/sql/st_functions.py
index 1cd944cb..592cbf74 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -43,6 +43,7 @@ __all__ = [
     "ST_Centroid",
     "ST_Collect",
     "ST_CollectionExtract",
+    "ST_ClosestPoint",
     "ST_ConcaveHull",
     "ST_ConvexHull",
     "ST_Difference",
@@ -375,6 +376,21 @@ def ST_CollectionExtract(collection: ColumnOrName, 
geom_type: Optional[Union[Col
     return _call_st_function("ST_CollectionExtract", args)
 
 
+@validate_argument_types
+def ST_ClosestPoint(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Returns the 2-dimensional point on geom1 that is closest to geom2. 
+    This is the first point of the shortest line between the geometries.
+
+    :param a: Geometry column to use in the calculation.
+    :type a: ColumnOrName
+    :param b: Geometry column to use in the calculation.
+    :type b: ColumnOrName
+    :return: the 2-dimensional point on a that is closest to b.
+    :rtype: Column
+    """
+    return _call_st_function("ST_ClosestPoint", (a, b))
+
+
 @validate_argument_types
 def ST_ConcaveHull(geometry: ColumnOrName, pctConvex: Union[ColumnOrName, 
float], allowHoles: Optional[Union[ColumnOrName, bool]] = None) -> Column:
     """Generate the cancave hull of a geometry column.
diff --git a/python/tests/sql/test_dataframe_api.py 
b/python/tests/sql/test_dataframe_api.py
index 5337604a..a0ba91ac 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -76,6 +76,7 @@ test_configurations = [
     (stf.ST_Centroid, ("geom",), "triangle_geom", "ST_PrecisionReduce(geom, 
2)", "POINT (0.67 0.33)"),
     (stf.ST_Collect, (lambda: f.expr("array(a, b)"),), "two_points", "", 
"MULTIPOINT Z (0 0 0, 3 0 4)"),
     (stf.ST_Collect, ("a", "b"), "two_points", "", "MULTIPOINT Z (0 0 0, 3 0 
4)"),
+    (stf.ST_ClosestPoint, ("point", "line",), "point_and_line", "", "POINT (0 
1)"),
     (stf.ST_CollectionExtract, ("geom",), "geom_collection", "", 
"MULTILINESTRING ((0 0, 1 0))"),
     (stf.ST_CollectionExtract, ("geom", 1), "geom_collection", "", "MULTIPOINT 
(0 0)"),
     (stf.ST_ConcaveHull, ("geom", 1.0), "triangle_geom", "", "POLYGON ((0 0, 1 
1, 1 0, 0 0))"),
diff --git a/python/tests/sql/test_function.py 
b/python/tests/sql/test_function.py
index 13137bec..a02a7ab0 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -899,6 +899,13 @@ class TestPredicateJoin(TestBase):
         for wkt, expected_wkt in geohash:
             assert wkt == expected_wkt
 
+    def test_st_closest_point(self):
+        expected = "POINT (0 1)"
+        actual_df = self.spark.sql("select 
ST_AsText(ST_ClosestPoint(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_collect_on_array_type(self):
         # given
         geometry_df = self.spark.createDataFrame([
diff --git a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala 
b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 31b11a53..e472fde6 100644
--- a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -99,6 +99,7 @@ object Catalog {
     function[ST_Y](),
     function[ST_Z](),
     function[ST_StartPoint](),
+    function[ST_ClosestPoint](),
     function[ST_Boundary](),
     function[ST_MinimumBoundingRadius](),
     
function[ST_MinimumBoundingCircle](BufferParameters.DEFAULT_QUADRANT_SEGMENTS),
diff --git 
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
 
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index df2d6155..676e1594 100644
--- 
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ 
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -601,6 +601,14 @@ case class ST_SetPoint(inputExpressions: Seq[Expression])
   }
 }
 
+case class ST_ClosestPoint(inputExpressions: Seq[Expression])
+  extends InferredExpression(Functions.closestPoint _) {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
+    copy(inputExpressions = newChildren)
+  }
+}
+
 case class ST_IsRing(inputExpressions: Seq[Expression])
   extends InferredExpression(ST_IsRing.isRing _) {
 
diff --git 
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
 
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
index 881d4fd4..4bfec2ec 100644
--- 
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
+++ 
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -73,6 +73,9 @@ object st_functions extends DataFrameAPI {
   def ST_Centroid(geometry: Column): Column = 
wrapExpression[ST_Centroid](geometry)
   def ST_Centroid(geometry: String): Column = 
wrapExpression[ST_Centroid](geometry)
 
+  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_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/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala 
b/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
index f534bd7f..9a76e76f 100644
--- 
a/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
+++ 
b/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -702,6 +702,14 @@ class dataFrameAPITestScala extends TestBaseScala {
       assert(actualResult == expectedResult)
     }
 
+    it("Passed ST_ClosestPoint") {
+      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_ClosestPoint("g1", "g2"))
+      val expected = "POINT (0 1)"
+      val actual = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      assertEquals(expected, 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/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala 
b/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index f413dc07..c8fef629 100644
--- a/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -1913,6 +1913,22 @@ class functionTestScala extends TestBaseScala with 
Matchers with GeometrySample
     }
   }
 
+  it("should pass ST_ClosestPoint") {
+    val geomTestCases = Map(
+      ("'POINT (160 40)'", "'LINESTRING (10 30, 50 50, 30 110, 70 90, 180 140, 
130 190)'") -> "POINT (160 40)",
+      ("'LINESTRING (0 0, 100 0)'", "'LINESTRING (0 0, 50 50, 100 0)'") -> 
"POINT (0 0)"
+    )
+    for (((geom), expectedResult) <- geomTestCases) {
+      val g1 = geom._1
+      val g2 = geom._2
+      val df = sparkSession.sql(s"SELECT ST_ClosestPoint(ST_GeomFromWKT($g1), 
ST_GeomFromWKT($g2))")
+      val actual = df.take(1)(0).get(0).asInstanceOf[Geometry].toText
+      val expected = expectedResult
+      assertEquals(expected, actual)
+
+    }
+  }
+
   it("Should pass ST_AreaSpheroid") {
     val geomTestCases = Map(
       ("'POINT (51.3168 -0.56)'") -> "0.0",

Reply via email to