You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2024/05/29 03:16:29 UTC

(sedona) branch SEDONA-573 created (now 6e1feb6c2)

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

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


      at 6e1feb6c2 [TASK-70] Add ST_HasM (#153)

This branch includes the following new commits:

     new 6e1feb6c2 [TASK-70] Add ST_HasM (#153)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(sedona) 01/01: [TASK-70] Add ST_HasM (#153)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6e1feb6c2169c632bfbf5244f2fd1bf7deeea1c3
Author: Furqaan Khan <46...@users.noreply.github.com>
AuthorDate: Fri Apr 5 01:06:00 2024 -0400

    [TASK-70] Add ST_HasM (#153)
---
 .../java/org/apache/sedona/common/Functions.java   |  5 +++++
 .../org/apache/sedona/common/FunctionsTest.java    | 15 +++++++++++++++
 docs/api/flink/Function.md                         | 22 ++++++++++++++++++++++
 docs/api/sql/Function.md                           | 22 ++++++++++++++++++++++
 .../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                  | 11 +++++++++++
 python/tests/sql/test_dataframe_api.py             |  2 ++
 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, 122 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 f1a1988a4..ba9971c52 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -456,6 +456,11 @@ public class Functions {
         return min == Double.MAX_VALUE ? null : min;
     }
 
+    public static boolean hasM(Geometry geom) {
+        Coordinate coord = geom.getCoordinate();
+        return !Double.isNaN(coord.getM());
+    }
+
     public static Geometry flipCoordinates(Geometry geometry) {
         GeomUtils.flipCoordinates(geometry);
         return geometry;
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 9107670aa..b0ad805b4 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,21 @@ public class FunctionsTest extends TestBase {
         assertEquals(actualResult, expectedResult);
     }
 
+    @Test
+    public void hasM() throws ParseException {
+        Geometry geom = Constructors.geomFromWKT("POINT ZM(1 2 3 4)", 0);
+        assertTrue(Functions.hasM(geom));
+
+        geom = Constructors.geomFromWKT("POINT Z(1 2 3)", 0);
+        assertFalse(Functions.hasM(geom));
+
+        geom = Constructors.geomFromWKT("POINT(34 25)", 0);
+        assertFalse(Functions.hasM(geom));
+
+        geom = Constructors.geomFromWKT("POLYGON ZM ((30 10 5 1, 40 40 10 2, 20 40 15 3, 10 20 20 4, 30 10 5 1))", 0);
+        assertTrue(Functions.hasM(geom));
+    }
+
     @Test
     public void testM() throws ParseException {
         Geometry geom = Constructors.geomFromWKT("POINT ZM(1 2 3 4)", 0);
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index fe3f87e38..9de30220b 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -1552,6 +1552,28 @@ Output:
 [POLYGON ((1.0057629565405093 1.9984665139177547, 1.0037116327309097 2.0018325249140068, 0.999727799357053 2.001163270465665, 0.9977951427833316 1.997128228393235, 0.9998461908217928 1.993762152933182, 1.0038301712104316 1.9944311839965523, 1.0057629565405093 1.9984665139177547))]
 ```
 
+## ST_HasM
+
+Introduction: Checks for the presence of M coordinate values representing measures or linear references. Returns true if the input geometry includes an M coordinate, false otherwise.
+
+Format: `ST_HasM(geom: Geometry)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_HasM(
+        ST_GeomFromWKT('POLYGON ZM ((30 10 5 1, 40 40 10 2, 20 40 15 3, 10 20 20 4, 30 10 5 1))')
+)
+```
+
+Output:
+
+```
+True
+```
+
 ## ST_HausdorffDistance
 
 Introduction: Returns a discretized (and hence approximate) [Hausdorff distance](https://en.wikipedia.org/wiki/Hausdorff_distance) between the given 2 geometries.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 7ea708b27..70155d0c6 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -1558,6 +1558,28 @@ Output:
 [POLYGON ((1.0057629565405093 1.9984665139177547, 1.0037116327309097 2.0018325249140068, 0.999727799357053 2.001163270465665, 0.9977951427833316 1.997128228393235, 0.9998461908217928 1.993762152933182, 1.0038301712104316 1.9944311839965523, 1.0057629565405093 1.9984665139177547))]
 ```
 
+## ST_HasM
+
+Introduction: Checks for the presence of M coordinate values representing measures or linear references. Returns true if the input geometry includes an M coordinate, false otherwise.
+
+Format: `ST_HasM(geom: Geometry)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_HasM(
+        ST_GeomFromWKT('POLYGON ZM ((30 10 5 1, 40 40 10 2, 20 40 15 3, 10 20 20 4, 30 10 5 1))')
+)
+```
+
+Output:
+
+```
+True
+```
+
 ## ST_HausdorffDistance
 
 Introduction: Returns a discretized (and hence approximate) [Hausdorff distance](https://en.wikipedia.org/wiki/Hausdorff_distance) between the given 2 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 bedf26a5c..100c7cb56 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -123,6 +123,7 @@ public class Catalog {
                 new Functions.ST_LineFromMultiPoint(),
                 new Functions.ST_LineMerge(),
                 new Functions.ST_LineSubstring(),
+                new Functions.ST_HasM(),
                 new Functions.ST_M(),
                 new Functions.ST_MakeLine(),
                 new Functions.ST_Polygon(),
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 bd79b328a..150da9e99 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
@@ -749,6 +749,14 @@ public class Functions {
         }
     }
 
+    public static class ST_HasM extends ScalarFunction {
+        @DataTypeHint("Boolean")
+        public Boolean eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
+            Geometry geom = (Geometry) o;
+            return org.apache.sedona.common.Functions.hasM(geom);
+        }
+    }
+
     public static class ST_M extends  ScalarFunction {
         @DataTypeHint("Double")
         public Double 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 ec2b8e777..e9b4053a9 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -699,6 +699,13 @@ public class FunctionTest extends TestBase{
         assertEquals(7.89, first(pointTable).getField(0));
     }
 
+    @Test
+    public void testHasM() {
+        Table polyTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POLYGON ZM ((30 10 5 1, 40 40 10 2, 20 40 15 3, 10 20 20 4, 30 10 5 1))') as poly");
+        boolean actual = (boolean) first(polyTable.select(call(Functions.ST_HasM.class.getSimpleName(), $("poly")))).getField(0);
+        assertTrue(actual);
+    }
+
     @Test
     public void testM() {
         Table pointTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POINT ZM(1 2 3 4)') AS point");
diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py
index 2be621456..2b45d9e03 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -857,6 +857,17 @@ 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_HasM(geom: ColumnOrName) -> Column:
+    """Check whether geometry has M coordinate
+
+    :param geom: Geometry
+    :type geom: ColumnOrName
+    :return: True if geometry has M coordinate, else False
+    :rtype: Column
+    """
+    return _call_st_function("ST_HasM", geom)
+
 
 @validate_argument_types
 def ST_M(geom: ColumnOrName) -> Column:
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
index 977c460ca..fd353b876 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -143,6 +143,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_HasM, ("point",), "4D_point", "", True),
     (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)"),
@@ -312,6 +313,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_HasM, (None,)),
     (stf.ST_M, (None,)),
     (stf.ST_MakeValid, (None,)),
     (stf.ST_MakePolygon, (None,)),
diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py
index fc9e5f5da..bc0aeb990 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -931,6 +931,11 @@ class TestPredicateJoin(TestBase):
         # Then
         assert subdivided.count() == 16
 
+    def test_st_has_m(self):
+        baseDf = self.spark.sql("SELECT ST_GeomFromWKT('POLYGON ZM ((30 10 5 1, 40 40 10 2, 20 40 15 3, 10 20 20 4, 30 10 5 1))') as poly")
+        actual = baseDf.selectExpr("ST_HasM(poly)")
+        assert actual
+
     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]
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 988a536cb..a630aa4ad 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
@@ -109,6 +109,7 @@ object Catalog {
     function[ST_Snap](),
     function[ST_ClosestPoint](),
     function[ST_Boundary](),
+    function[ST_HasM](),
     function[ST_M](),
     function[ST_MinimumBoundingRadius](),
     function[ST_MinimumBoundingCircle](BufferParameters.DEFAULT_QUADRANT_SEGMENTS * 6),
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 d4f8cd405..b395dd0f8 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_HasM(inputExpressions: Seq[Expression])
+  extends InferredExpression(Functions.hasM _) {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
+
 case class ST_M(inputExpressions: Seq[Expression])
   extends InferredExpression(Functions.m _) {
 
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 cbeec10dc..dd7395bd1 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_HasM(geoms: Column): Column = wrapExpression[ST_HasM](geoms)
+  def ST_HasM(geoms: String): Column = wrapExpression[ST_HasM](geoms)
+
   def ST_M(geoms: Column): Column = wrapExpression[ST_M](geoms)
   def ST_M(geoms: String): Column = wrapExpression[ST_M](geoms)
 
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 01129a4fa..dd66f5b41 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_HasM") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ZM ((30 10 5 1, 40 40 10 2, 20 40 15 3, 10 20 20 4, 30 10 5 1))') as poly")
+      val actual = baseDf.select(ST_HasM("poly")).first().getBoolean(0)
+      assert(actual)
+    }
+
     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)
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 2b19744f5..5dee828db 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
@@ -385,6 +385,12 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
       assert(!testtable.take(1)(0).get(1).asInstanceOf[Boolean])
     }
 
+    it("Passed ST_HasM") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ZM ((30 10 5 1, 40 40 10 2, 20 40 15 3, 10 20 20 4, 30 10 5 1))') as poly")
+      val actual = baseDf.selectExpr("ST_HasM(poly)").first().getBoolean(0)
+      assert(actual)
+    }
+
     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)