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 02:15:44 UTC

(sedona) 01/02: [TASK-212] Add ST_PointZM (#143)

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

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

commit 50cfe9ef538edb94cf59a68c02d60bb696d74db2
Author: Pranav Toggi <pr...@gmail.com>
AuthorDate: Sun Mar 31 00:47:09 2024 -0400

    [TASK-212] Add ST_PointZM (#143)
    
    * Add ST_PointZM
    
    * fix snowflake test
    
    * fix snowflake test
    
    * Remove snowflake support
    
    * Add flink test
    
    * Add Dataframe api test; remove show()
    
    * fix python test
    
    * Add scala dataframe api tests
    
    * fix test
---
 .../org/apache/sedona/common/Constructors.java     | 15 ++++++++++++
 .../org/apache/sedona/common/ConstructorsTest.java |  9 ++++++++
 docs/api/flink/Constructor.md                      | 25 ++++++++++++++++++++
 docs/api/snowflake/vector-data/Constructor.md      |  2 +-
 docs/api/sql/Constructor.md                        | 25 ++++++++++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |  1 +
 .../sedona/flink/expressions/Constructors.java     | 12 ++++++++++
 .../org/apache/sedona/flink/ConstructorTest.java   | 27 ++++++++++++++++++++++
 python/sedona/sql/st_constructors.py               | 20 ++++++++++++++++
 python/tests/sql/test_dataframe_api.py             |  3 +++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../sql/sedona_sql/expressions/Constructors.scala  |  8 +++++++
 .../sedona_sql/expressions/st_constructors.scala   | 10 ++++++++
 .../apache/sedona/sql/constructorTestScala.scala   |  5 ++++
 .../apache/sedona/sql/dataFrameAPITestScala.scala  |  8 +++++++
 15 files changed, 170 insertions(+), 1 deletion(-)

diff --git a/common/src/main/java/org/apache/sedona/common/Constructors.java b/common/src/main/java/org/apache/sedona/common/Constructors.java
index f348b2017..0d28c1405 100644
--- a/common/src/main/java/org/apache/sedona/common/Constructors.java
+++ b/common/src/main/java/org/apache/sedona/common/Constructors.java
@@ -136,6 +136,21 @@ public class Constructors {
         return geometryFactory.createPoint(new Coordinate(x, y, z));
     }
 
+    /**
+     * Creates a point from the given coordinate.
+     *
+     * @param x the x value
+     * @param y the y value
+     * @param z the z value
+     * @param m the m value
+     * @param srid Set to 0 if unknown
+     * @return The point geometry
+     */
+    public static Geometry pointZM(double x, double y, double z, double m, int srid) {
+        GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
+        return geometryFactory.createPoint(new CoordinateXYZM(x, y, z, m));
+    }
+
     public static Geometry geomFromText(String geomString, String geomFormat, GeometryType geometryType) {
         FileDataSplitter fileDataSplitter = FileDataSplitter.getFileDataSplitter(geomFormat);
         FormatUtils<Geometry> formatMapper = new FormatUtils<>(fileDataSplitter, false, geometryType);
diff --git a/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java b/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
index d7b92f3a7..a9abb84de 100644
--- a/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
@@ -143,4 +143,13 @@ public class ConstructorsTest {
         assertEquals(4326, point.getSRID());
         assertEquals("POINT Z(0 1 2)", Functions.asWKT(point));
     }
+
+    @Test
+    public void pointZM() {
+        Geometry point = Constructors.pointZM(0.0d, 1.0d, 2.0d, 10.0, 4326);
+
+        assertTrue(point instanceof Point);
+        assertEquals(4326, point.getSRID());
+        assertEquals("POINT ZM(0 1 2 10)", Functions.asWKT(point));
+    }
 }
diff --git a/docs/api/flink/Constructor.md b/docs/api/flink/Constructor.md
index c63a8f84c..d8fa300b3 100644
--- a/docs/api/flink/Constructor.md
+++ b/docs/api/flink/Constructor.md
@@ -480,6 +480,31 @@ Output:
 POINT Z(1.2345 2.3456 3.4567)
 ```
 
+## ST_PointZM
+
+Introduction: Construct a Point from X, Y, Z, M and an optional srid. If srid is not set, it defaults to 0 (unknown).
+Must use ST_AsEWKT function to print the Z and M coordinates.
+
+Format:
+
+`ST_PointZM (X: Double, Y: Double, Z: Double, M: Double)`
+
+`ST_PointZM (X: Double, Y: Double, Z: Double, M: Double, srid: Integer)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_AsEWKT(ST_PointZM(1.2345, 2.3456, 3.4567, 100))
+```
+
+Output:
+
+```
+POINT ZM(1.2345 2.3456 3.4567, 100)
+```
+
 ## ST_PolygonFromEnvelope
 
 Introduction: Construct a Polygon from MinX, MinY, MaxX, MaxY.
diff --git a/docs/api/snowflake/vector-data/Constructor.md b/docs/api/snowflake/vector-data/Constructor.md
index e7b98f9dc..9d37db3f7 100644
--- a/docs/api/snowflake/vector-data/Constructor.md
+++ b/docs/api/snowflake/vector-data/Constructor.md
@@ -392,7 +392,7 @@ Introduction: Construct a Point from X and Y
 Format: `ST_Point (X:decimal, Y:decimal)`
 
 In `v1.4.0` an optional Z parameter was removed to be more consistent with other spatial SQL implementations.
-If you are upgrading from an older version of Sedona - please use ST_PointZ to create 3D points.
+If you are upgrading from an older version of Sedona - please use ST_PointZ or ST_PointZM to create 3D points.
 
 SQL example:
 
diff --git a/docs/api/sql/Constructor.md b/docs/api/sql/Constructor.md
index 9738f7024..940f73d6c 100644
--- a/docs/api/sql/Constructor.md
+++ b/docs/api/sql/Constructor.md
@@ -542,6 +542,31 @@ Output:
 POINT Z(1.2345 2.3456 3.4567)
 ```
 
+## ST_PointZM
+
+Introduction: Construct a Point from X, Y, Z, M and an optional srid. If srid is not set, it defaults to 0 (unknown).
+Must use ST_AsEWKT function to print the Z and M coordinates.
+
+Format:
+
+`ST_PointZM (X: Double, Y: Double, Z: Double, M: Double)`
+
+`ST_PointZM (X: Double, Y: Double, Z: Double, M: Double, srid: Integer)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_AsEWKT(ST_PointZM(1.2345, 2.3456, 3.4567, 100))
+```
+
+Output:
+
+```
+POINT ZM(1.2345 2.3456 3.4567, 100)
+```
+
 ## ST_PolygonFromEnvelope
 
 Introduction: Construct a Polygon from MinX, MinY, MaxX, MaxY.
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..101037435 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -24,6 +24,7 @@ public class Catalog {
                 new Aggregators.ST_Union_Aggr(),
                 new Constructors.ST_Point(),
                 new Constructors.ST_PointZ(),
+                new Constructors.ST_PointZM(),
                 new Constructors.ST_PointFromText(),
                 new Constructors.ST_MakePoint(),
                 new Constructors.ST_LineStringFromText(),
diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java
index 7054e32d5..a4ce11727 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java
@@ -54,6 +54,18 @@ public class Constructors {
         }
     }
 
+    public static class ST_PointZM extends ScalarFunction {
+        @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
+        public Geometry eval(@DataTypeHint("Double") Double x, @DataTypeHint("Double") Double y, @DataTypeHint("Double") Double z, @DataTypeHint("Double") Double m) throws ParseException {
+            return eval(x, y, z, m,0);
+        }
+
+        @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
+        public Geometry eval(@DataTypeHint("Double") Double x, @DataTypeHint("Double") Double y, @DataTypeHint("Double") Double z, @DataTypeHint("Double") Double m, @DataTypeHint("Integer") Integer srid) throws ParseException {
+            return org.apache.sedona.common.Constructors.pointZM(x, y, z, m, srid);
+        }
+    }
+
     public static class ST_PointFromText extends ScalarFunction {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint("String") String s, @DataTypeHint("String") String inputDelimiter) throws ParseException {
diff --git a/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java b/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
index 5470f96d0..811ab6f81 100644
--- a/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
@@ -99,6 +99,33 @@ public class ConstructorTest extends TestBase{
         assertEquals(5.0, result.getCoordinate().getZ(), 1e-6);
     }
 
+    @Test
+    public void testPointZM() {
+        List<Row> data = new ArrayList<>();
+        data.add(Row.of(2.0, 2.0, 5.0, 100.0, "point"));
+        String[] colNames = new String[]{"x", "y", "z", "m", "name_point"};
+
+        TypeInformation<?>[] colTypes = {
+                BasicTypeInfo.DOUBLE_TYPE_INFO,
+                BasicTypeInfo.DOUBLE_TYPE_INFO,
+                BasicTypeInfo.DOUBLE_TYPE_INFO,
+                BasicTypeInfo.DOUBLE_TYPE_INFO,
+                BasicTypeInfo.STRING_TYPE_INFO};
+        RowTypeInfo typeInfo = new RowTypeInfo(colTypes, colNames);
+        DataStream<Row> ds = env.fromCollection(data).returns(typeInfo);
+        Table pointTable = tableEnv.fromDataStream(ds);
+
+        Table geomTable = pointTable
+                .select(call(Constructors.ST_PointZM.class.getSimpleName(), $(colNames[0]), $(colNames[1]), $(colNames[2]), $(colNames[3]))
+                        .as(colNames[4]));
+
+        Point result = first(geomTable)
+                .getFieldAs(colNames[4]);
+
+        assertEquals(5.0, result.getCoordinate().getZ(), 1e-6);
+        assertEquals(100.0, result.getCoordinate().getM(), 1e-6);
+    }
+
     @Test
     public void testMakePoint() {
         List<Row> data = new ArrayList<>();
diff --git a/python/sedona/sql/st_constructors.py b/python/sedona/sql/st_constructors.py
index 11b561bee..d73be2260 100644
--- a/python/sedona/sql/st_constructors.py
+++ b/python/sedona/sql/st_constructors.py
@@ -205,6 +205,26 @@ def ST_PointZ(x: ColumnOrNameOrNumber, y: ColumnOrNameOrNumber, z: ColumnOrNameO
     args = (x, y, z) if srid is None else (x, y, z, srid)
     return _call_constructor_function("ST_PointZ", args)
 
+@validate_argument_types
+def ST_PointZM(x: ColumnOrNameOrNumber, y: ColumnOrNameOrNumber, z: ColumnOrNameOrNumber, m: ColumnOrNameOrNumber,srid: Optional[ColumnOrNameOrNumber] = None) -> Column:
+    """Generates a 3D point geometry column from numeric values.
+
+    :param x: Either a number or numeric column representing the X coordinate of a point.
+    :type x: ColumnOrNameOrNumber
+    :param y: Either a number or numeric column representing the Y coordinate of a point.
+    :type y: ColumnOrNameOrNumber
+    :param z: Either a number or numeric column representing the Z coordinate of a point, if None then a 2D point is generated, defaults to None
+    :type z: ColumnOrNameOrNumber
+    :param m: Either a number or numeric column representing the M value of a point.
+    :type m: ColumnOrNameOrNumber
+    :param srid: The srid of the point. Defaults to 0 (unknown).
+    :type srid: Optional[ColumnOrNameOrNumber], optional
+    :return: Point geometry column generated from the coordinate values.
+    :rtype: Column
+    """
+    args = (x, y, z, m) if srid is None else (x, y, z, m, srid)
+    return _call_constructor_function("ST_PointZM", args)
+
 @validate_argument_types
 def ST_PointFromText(coords: ColumnOrName, delimiter: ColumnOrName) -> Column:
     """Generate a point geometry column from coordinates separated by a delimiter and stored
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
index 977c460ca..e193efa1a 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -58,6 +58,7 @@ test_configurations = [
     (stc.ST_MPolyFromText, ("mpoly", 4326), "constructor", "" , "MULTIPOLYGON (((0 0, 20 0, 20 20, 0 20, 0 0), (5 5, 5 7, 7 7, 7 5, 5 5)))"),
     (stc.ST_MLineFromText, ("mline", ), "constructor", "" , "MULTILINESTRING ((1 2, 3 4), (4 5, 6 7))"),
     (stc.ST_MLineFromText, ("mline", 4326), "constructor", "" , "MULTILINESTRING ((1 2, 3 4), (4 5, 6 7))"),
+    (stc.ST_PointZM, ("x", "y", "z", "m", "srid"), "x_y_z_m_srid", "", "POINT Z (1 2 3)"),
     (stc.ST_PointFromText, ("single_point", lambda: f.lit(',')), "constructor", "", "POINT (0 1)"),
     (stc.ST_MakePoint, ("x", "y", "z"), "constructor", "", "POINT Z (0 1 2)"),
     (stc.ST_PolygonFromEnvelope, ("minx", "miny", "maxx", "maxy"), "min_max_x_y", "", "POLYGON ((0 1, 0 3, 2 3, 2 1, 0 1))"),
@@ -431,6 +432,8 @@ class TestDataFrameAPI(TestBase):
             return TestDataFrameAPI.spark.sql("SELECT 'SRID=4269;LINESTRING (1 2, 3 4)' AS ewkt")
         elif request.param == "min_max_x_y":
             return TestDataFrameAPI.spark.sql("SELECT 0.0 AS minx, 1.0 AS miny, 2.0 AS maxx, 3.0 AS maxy")
+        elif request.param == "x_y_z_m_srid":
+            return TestDataFrameAPI.spark.sql("SELECT 1.0 AS x, 2.0 AS y, 3.0 AS z, 100.9 AS m, 4326 AS srid")
         elif request.param == "multipoint_geom":
             return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('MULTIPOINT((10 40), (40 30), (20 20), (30 10))') AS multipoint")
         elif request.param == "null":
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..6de69808e 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
@@ -53,6 +53,7 @@ object Catalog {
     function[ST_Point](),
     function[ST_MakePoint](null, null),
     function[ST_PointZ](0),
+    function[ST_PointZM](0),
     function[ST_PolygonFromEnvelope](),
     function[ST_Contains](),
     function[ST_Intersects](),
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
index 6ed6d7335..1f4966dd9 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
@@ -246,6 +246,14 @@ case class ST_PointZ(inputExpressions: Seq[Expression])
   }
 }
 
+case class ST_PointZM(inputExpressions: Seq[Expression])
+  extends InferredExpression(Constructors.pointZM _) {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
+
 case class ST_MakePoint(inputExpressions: Seq[Expression])
   extends InferredExpression(nullTolerantInferrableFunction4(Constructors.makePoint)) {
 
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
index 02d055a1f..51eaadab9 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
@@ -81,6 +81,16 @@ object st_constructors extends DataFrameAPI {
 
   def ST_PointZ(x: Double, y: Double, z: Double, srid: Int): Column = wrapExpression[ST_PointZ](x, y, z, srid)
 
+  def ST_PointZM(x: Column, y: Column, z: Column, m: Column): Column = wrapExpression[ST_PointZM](x, y, z, m, 0)
+  def ST_PointZM(x: String, y: String, z: String, m: String): Column = wrapExpression[ST_PointZM](x, y, z, m, 0)
+  def ST_PointZM(x: Double, y: Double, z: Double, m: Double): Column = wrapExpression[ST_PointZM](x, y, z, m, 0)
+
+  def ST_PointZM(x: Column, y: Column, z: Column, m: Column, srid: Column): Column = wrapExpression[ST_PointZM](x, y, z, m, srid)
+
+  def ST_PointZM(x: String, y: String, z: String, m: String, srid: Column): Column = wrapExpression[ST_PointZM](x, y, z, m, srid)
+
+  def ST_PointZM(x: Double, y: Double, z: Double, m: Double, srid: Int): Column = wrapExpression[ST_PointZM](x, y, z, m, srid)
+
   def ST_MakePoint(x: Column, y: Column): Column = wrapExpression[ST_MakePoint](x, y, null, null)
   def ST_MakePoint(x: String, y: String): Column = wrapExpression[ST_MakePoint](x, y, null, null)
   def ST_MakePoint(x: Double, y: Double): Column = wrapExpression[ST_MakePoint](x, y, null, null)
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala b/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
index 9d6b416bd..c77a392c3 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
@@ -76,6 +76,11 @@ class constructorTestScala extends TestBaseScala {
       assert(pointDf.count() == 1)
     }
 
+    it("Passed ST_PointZM") {
+      val pointDf = sparkSession.sql("SELECT ST_PointZM(1.2345, 2.3456, 3.4567, 100)")
+      assert(pointDf.count() == 1)
+    }
+
     it("Passed ST_PointZ null safety") {
       val pointDf = sparkSession.sql("SELECT ST_PointZ(null, null, null)")
       assert(pointDf.count() == 1)
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..842fc0e70 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
@@ -1461,5 +1461,13 @@ class dataFrameAPITestScala extends TestBaseScala {
       val esriValidityReason = esriValidityTable.take(1)(0).getString(0)
       assertEquals("Interior is disconnected at or near point (1.0, 1.0, NaN)", esriValidityReason)
     }
+
+    it("Passed ST_PointZM") {
+      val pointDf = sparkSession.sql("SELECT ST_PointZM(1,2,3,100) as point1, ST_PointZM(1,2,3,100,4326) as point2")
+      val point1 = pointDf.select(ST_AsEWKT("point1")).take(1)(0).getString(0)
+      val point2 = pointDf.select(ST_AsEWKT("point2")).take(1)(0).getString(0)
+      assertEquals("POINT ZM(1 2 3 100)", point1)
+      assertEquals("SRID=4326;POINT ZM(1 2 3 100)", point2)
+    }
   }
 }