This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch SEDONA-567
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit f9058542b360cdb0babc5fa1bc22b8c318a08caa
Author: Furqaan Khan <[email protected]>
AuthorDate: Fri Mar 29 23:37:31 2024 -0400

    [TASK-70] Add ST_M (#144)
    
    * feat: add ST_M
    
    * fix: snowflake registration
    
    * fix: remove function from snowflake
    
    * docs: remove function from snowflake docs
---
 .../java/org/apache/sedona/common/Functions.java    |  7 +++++++
 .../org/apache/sedona/common/FunctionsTest.java     | 21 +++++++++++++++++++++
 docs/api/flink/Function.md                          | 20 ++++++++++++++++++++
 docs/api/sql/Function.md                            | 20 ++++++++++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java  |  1 +
 .../apache/sedona/flink/expressions/Functions.java  |  8 ++++++++
 .../java/org/apache/sedona/flink/FunctionTest.java  |  7 +++++++
 python/sedona/sql/st_functions.py                   | 12 ++++++++++++
 python/tests/sql/test_dataframe_api.py              |  4 ++++
 python/tests/sql/test_function.py                   |  5 +++++
 .../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   |  6 ++++++
 .../org/apache/sedona/sql/functionTestScala.scala   |  6 ++++++
 15 files changed, 129 insertions(+)

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 5cec2a42d..6093ad94a 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -390,6 +390,13 @@ public class Functions {
         return null;
     }
 
+    public static Double m(Geometry geom) {
+        if (geom instanceof Point) {
+            return geom.getCoordinate().getM();
+        }
+        return null;
+    }
+
     public static double xMin(Geometry geometry) {
         Coordinate[] points = geometry.getCoordinates();
         double min = Double.MAX_VALUE;
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 701a24594..ad9741849 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -369,6 +369,27 @@ public class FunctionsTest extends TestBase {
         assertEquals(actualResult, expectedResult);
     }
 
+    @Test
+    public void testM() throws ParseException {
+        Geometry geom = Constructors.geomFromWKT("POINT ZM(1 2 3 4)", 0);
+        double actual = Functions.m(geom);
+        double expected = 4;
+        assertEquals(expected, actual, FP_TOLERANCE2);
+
+        geom = Constructors.geomFromWKT("POINT M(1 2 3)", 0);
+        actual = Functions.m(geom);
+        expected = 3;
+        assertEquals(expected, actual, FP_TOLERANCE2);
+
+        geom = Constructors.geomFromWKT("POINT Z(1 2 3)", 0);
+        actual = Functions.m(geom);
+        assertTrue(Double.isNaN(actual));
+
+        geom = Constructors.geomFromWKT("LINESTRING ZM(1 2 3 4, 2 3 4 5)", 0);
+        Double actualRes = Functions.m(geom);
+        assertNull(actualRes);
+    }
+
     @Test
     public void dimensionGeometry3D() {
         Point point3D = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1, 1));
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 5c6b2f6f3..c6cef4343 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -2006,6 +2006,26 @@ Output:
 LINESTRING (69.28469348539744 94.28469348539744, 100 125, 111.70035626068274 
140.21046313888758)
 ```
 
+## ST_M
+
+Introduction: Returns M Coordinate of given Point, null otherwise.
+
+Format: `ST_M(geom: Geometry)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_M(ST_MakePoint(1, 2, 3, 4))
+```
+
+Output:
+
+```
+4.0
+```
+
 ## ST_MakeLine
 
 Introduction: Creates a LineString containing the points of Point, MultiPoint, 
or LineString geometries. Other geometry types cause an error.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 814c9c376..317b8b4a0 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -2013,6 +2013,26 @@ Output:
 LINESTRING (69.28469348539744 94.28469348539744, 100 125, 111.70035626068274 
140.21046313888758)
 ```
 
+## ST_M
+
+Introduction: Returns M Coordinate of given Point, null otherwise.
+
+Format: `ST_M(geom: Geometry)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_M(ST_MakePoint(1, 2, 3, 4))
+```
+
+Output:
+
+```
+4.0
+```
+
 ## ST_MakeLine
 
 Introduction: Creates a LineString containing the points of Point, MultiPoint, 
or LineString geometries. Other geometry types cause an error.
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 efd07c3ca..2452ea03a 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -121,6 +121,7 @@ public class Catalog {
                 new Functions.ST_LineFromMultiPoint(),
                 new Functions.ST_LineMerge(),
                 new Functions.ST_LineSubstring(),
+                new Functions.ST_M(),
                 new Functions.ST_MakeLine(),
                 new Functions.ST_Polygon(),
                 new Functions.ST_Polygonize(),
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 150c2048a..b06b97d3a 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
@@ -741,6 +741,14 @@ public class Functions {
         }
     }
 
+    public static class ST_M extends  ScalarFunction {
+        @DataTypeHint("Double")
+        public Double eval(@DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class) Object o) {
+            Geometry geom = (Geometry) o;
+            return org.apache.sedona.common.Functions.m(geom);
+        }
+    }
+
     public static class ST_MakeLine 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 o1,
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 c8d1a2f2e..e9f4af0f9 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -692,6 +692,13 @@ public class FunctionTest extends TestBase{
         assertEquals(7.89, first(pointTable).getField(0));
     }
 
+    @Test
+    public void testM() {
+        Table pointTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POINT 
ZM(1 2 3 4)') AS point");
+        double actual = (double) 
first(pointTable.select(call(Functions.ST_M.class.getSimpleName(), 
$("point")))).getField(0);
+        assertEquals(4, actual, FP_TOLERANCE);
+    }
+
     @Test
     public void testZMax() {
         Table polygonTable = tableEnv.sqlQuery("SELECT 
ST_GeomFromWKT('LINESTRING(1 3 4, 5 6 7)') AS " + polygonColNames[0]);
diff --git a/python/sedona/sql/st_functions.py 
b/python/sedona/sql/st_functions.py
index dfd9ae13c..4afd63c46 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -858,6 +858,18 @@ def ST_LineSubstring(line_string: ColumnOrName, 
start_fraction: ColumnOrNameOrNu
     return _call_st_function("ST_LineSubstring", (line_string, start_fraction, 
end_fraction))
 
 
+@validate_argument_types
+def ST_M(geom: ColumnOrName) -> Column:
+    """Return the M coordinate of a point geometry.
+
+    :param point: Point geometry column to get the coordinate for.
+    :type point: ColumnOrName
+    :return: M coordinate of the point geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_M", geom)
+
+
 @validate_argument_types
 def ST_MakeLine(geom1: ColumnOrName, geom2: Optional[ColumnOrName] = None) -> 
Column:
     """Creates a LineString containing the points of Point, MultiPoint, or 
LineString geometries. Other geometry types cause an error.
diff --git a/python/tests/sql/test_dataframe_api.py 
b/python/tests/sql/test_dataframe_api.py
index 56ec8212a..f9a8aab53 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -141,6 +141,7 @@ test_configurations = [
     (stf.ST_LineLocatePoint, ("line", "point"), "line_and_point", "", 0.5),
     (stf.ST_LineMerge, ("geom",), "multiline_geom", "", "LINESTRING (0 0, 1 0, 
1 1, 0 0)"),
     (stf.ST_LineSubstring, ("line", 0.5, 1.0), "linestring_geom", "", 
"LINESTRING (2.5 0, 3 0, 4 0, 5 0)"),
+    (stf.ST_M, ("point",), "4D_point", "", 4.0),
     (stf.ST_MakeValid, ("geom",), "invalid_geom", "", "MULTIPOLYGON (((1 5, 3 
3, 1 1, 1 5)), ((5 3, 7 5, 7 1, 5 3)))"),
     (stf.ST_MakeLine, ("line1", "line2"), "two_lines", "", "LINESTRING (0 0, 1 
1, 0 0, 3 2)"),
     (stf.ST_Polygon, ("geom", 4236), "closed_linestring_geom", "", "POLYGON 
((0 0, 1 0, 1 1, 0 0))"),
@@ -305,6 +306,7 @@ wrong_type_configurations = [
     (stf.ST_LineSubstring, (None, 0.5, 1.0)),
     (stf.ST_LineSubstring, ("", None, 1.0)),
     (stf.ST_LineSubstring, ("", 0.5, None)),
+    (stf.ST_M, (None,)),
     (stf.ST_MakeValid, (None,)),
     (stf.ST_MakePolygon, (None,)),
     (stf.ST_MinimumBoundingCircle, (None,)),
@@ -429,6 +431,8 @@ class TestDataFrameAPI(TestBase):
             return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON 
((0 0, 1 0, 1 1, 0 0))') AS geom")
         elif request.param == "two_points":
             return TestDataFrameAPI.spark.sql("SELECT ST_PointZ(0.0, 0.0, 0.0) 
AS a, ST_PointZ(3.0, 0.0, 4.0) AS b")
+        elif request.param == "4D_point":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POINT 
ZM(1 2 3 4)') AS point")
         elif request.param == "invalid_geom":
             return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON 
((1 5, 1 1, 3 3, 5 3, 7 1, 7 5, 5 3, 3 3, 1 5))') AS geom")
         elif request.param == "overlapping_polys":
diff --git a/python/tests/sql/test_function.py 
b/python/tests/sql/test_function.py
index ea8497132..2e6d3414b 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -910,6 +910,11 @@ class TestPredicateJoin(TestBase):
         # Then
         assert subdivided.count() == 16
 
+    def test_st_m(self):
+        baseDf = self.spark.sql("SELECT ST_GeomFromWKT('POINT ZM (1 2 3 4)') 
AS point")
+        actual = baseDf.selectExpr("ST_M(point)").take(1)[0][0]
+        assert actual == 4.0
+
     def test_st_subdivide_explode_lateral(self):
         # Given
         geometry_df = self.__wkt_list_to_data_frame(
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 bd2e801ad..0a8cb29b2 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
@@ -108,6 +108,7 @@ object Catalog {
     function[ST_Snap](),
     function[ST_ClosestPoint](),
     function[ST_Boundary](),
+    function[ST_M](),
     function[ST_MinimumBoundingRadius](),
     
function[ST_MinimumBoundingCircle](BufferParameters.DEFAULT_QUADRANT_SEGMENTS * 
6),
     function[ST_EndPoint](),
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 4b9c09c03..599159d2b 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
@@ -511,6 +511,14 @@ case class ST_MinimumBoundingCircle(inputExpressions: 
Seq[Expression])
   }
 }
 
+case class ST_M(inputExpressions: Seq[Expression])
+  extends InferredExpression(Functions.m _) {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
+    copy(inputExpressions = newChildren)
+  }
+}
+
 
 /**
   * Return a linestring being a substring of the input one starting and ending 
at the given fractions of total 2d length.
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 b78917303..0a432fe55 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
@@ -212,6 +212,9 @@ object st_functions extends DataFrameAPI {
   def ST_LineSubstring(lineString: Column, startFraction: Column, endFraction: 
Column): Column = wrapExpression[ST_LineSubstring](lineString, startFraction, 
endFraction)
   def ST_LineSubstring(lineString: String, startFraction: Double, endFraction: 
Double): Column = wrapExpression[ST_LineSubstring](lineString, startFraction, 
endFraction)
 
+  def ST_M(geoms: Column): Column = wrapExpression[ST_M](geoms)
+  def ST_M(geoms: String): Column = wrapExpression[ST_M](geoms)
+
   def ST_MakeLine(geoms: Column): Column = wrapExpression[ST_MakeLine](geoms)
   def ST_MakeLine(geoms: String): Column = wrapExpression[ST_MakeLine](geoms)
   def ST_MakeLine(geom1: Column, geom2: Column): Column = 
wrapExpression[ST_MakeLine](geom1, geom2)
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 1ff67eeeb..ae2c3bb40 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
@@ -52,6 +52,12 @@ class dataFrameAPITestScala extends TestBaseScala {
       assert(actualResult == expectedResult)
     }
 
+    it("Passed ST_M") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POINT ZM (10 20 30 
40)') AS point")
+      val actual = baseDf.select(ST_M("point")).first().getDouble(0)
+      assert(actual == 40.0)
+    }
+
     it("passed st_makepoint") {
       val df = sparkSession.sql("SELECT 0.0 AS x, 1.0 AS y, 2.0 AS 
z").select(ST_AsText(ST_MakePoint("x", "y", "z")))
       val actualResult = df.take(1)(0).get(0).asInstanceOf[String]
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 3fee1e53c..8626409b4 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
@@ -378,6 +378,12 @@ class functionTestScala extends TestBaseScala with 
Matchers with GeometrySample
       assert(!testtable.take(1)(0).get(1).asInstanceOf[Boolean])
     }
 
+    it("Passed ST_M") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POINT ZM (1 2 3 
4)') AS point")
+      val actual = baseDf.selectExpr("ST_M(point)").first().getDouble(0)
+      assert(actual == 4.0)
+    }
+
     it("Passed ST_MakeLine") {
       val testtable = sparkSession.sql(
         """SELECT

Reply via email to