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 2022/11/23 01:00:23 UTC

[incubator-sedona] branch master updated: [SEDONA-197] Added ST_ZMax and ST_ZMin to Apache Sedona (#717)

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/incubator-sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new ab4d8266 [SEDONA-197] Added ST_ZMax and ST_ZMin to Apache Sedona (#717)
ab4d8266 is described below

commit ab4d8266fe9b48b451b13c40d0a44747936db311
Author: Varsha Ravindra <va...@gmail.com>
AuthorDate: Tue Nov 22 18:00:18 2022 -0700

    [SEDONA-197] Added ST_ZMax and ST_ZMin to Apache Sedona (#717)
---
 .../java/org/apache/sedona/common/Functions.java   | 22 +++++++++++++++
 docs/api/flink/Function.md                         | 31 +++++++++++++++++++++
 docs/api/sql/Function.md                           | 30 ++++++++++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |  2 ++
 .../apache/sedona/flink/expressions/Functions.java | 16 +++++++++++
 .../java/org/apache/sedona/flink/FunctionTest.java | 32 +++++++++++++++++++++-
 python/sedona/sql/st_functions.py                  | 24 ++++++++++++++++
 python/tests/sql/test_function.py                  | 10 +++++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  2 ++
 .../sql/sedona_sql/expressions/Functions.scala     | 26 ++++++++++++++++++
 .../sql/sedona_sql/expressions/st_functions.scala  |  6 ++++
 .../org/apache/sedona/sql/functionTestScala.scala  | 19 +++++++++++++
 12 files changed, 219 insertions(+), 1 deletion(-)

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 46755855..6ab744ca 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -155,6 +155,28 @@ public class Functions {
         return max;
     }
 
+    public static Double zMax(Geometry geometry) {
+        Coordinate[] points = geometry.getCoordinates();
+        double max = Double.MIN_VALUE;
+        for (int i=0; i < points.length; i++) {
+            if(java.lang.Double.isNaN(points[i].getZ()))
+                continue;
+            max = Math.max(points[i].getZ(), max);
+        }
+        return max == Double.MIN_VALUE ? null : max;
+    }
+
+    public static Double zMin(Geometry geometry) {
+        Coordinate[] points = geometry.getCoordinates();
+        double min = Double.MAX_VALUE;
+        for(int i=0; i < points.length; i++){
+            if(java.lang.Double.isNaN(points[i].getZ()))
+                continue;
+            min = Math.min(points[i].getZ(), min);
+        }
+        return min == Double.MAX_VALUE ? null : min;
+    }
+
     public static Geometry transform(Geometry geometry, String sourceCRS, String targetCRS)
         throws FactoryException, TransformException {
         return transform(geometry, sourceCRS, targetCRS, false);
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index e7a63000..1a630cf9 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -832,3 +832,34 @@ SELECT ST_Z(ST_POINT(0.0 25.0 11.0))
 ```
 
 Output: `11.0`
+
+## ST_ZMax
+
+Introduction: Returns Z maxima of the given geometry or null if there is no Z coordinate.
+
+Format: `ST_ZMax(geom: geometry)`
+
+Since: `v1.3.1`
+
+Spark SQL example:
+```SQL
+SELECT ST_ZMax(ST_GeomFromText('POLYGON((0 0 1, 1 1 1, 1 2 1, 1 1 1, 0 0 1))'))
+```
+
+Output: `1.0`
+
+## ST_ZMin
+
+Introduction: Returns Z minima of the given geometry or null if there is no Z coordinate.
+
+Format: `ST_ZMin(geom: geometry)`
+
+Since: `v1.3.1`
+
+Spark SQL example:
+```SQL
+SELECT ST_ZMin(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'))
+```
+
+Output: `4.0`
+
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 8453058c..df3982cd 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -1445,3 +1445,33 @@ SELECT ST_Z(ST_POINT(0.0 25.0 11.0))
 ```
 
 Output: `11.0`
+
+## ST_ZMax
+
+Introduction: Returns Z maxima of the given geometry or null if there is no Z coordinate.
+
+Format: `ST_ZMax(geom: geometry)`
+
+Since: `v1.3.1`
+
+Spark SQL example:
+```SQL
+SELECT ST_ZMax(ST_GeomFromText('POLYGON((0 0 1, 1 1 1, 1 2 1, 1 1 1, 0 0 1))'))
+```
+
+Output: `1.0`
+
+## ST_ZMin
+
+Introduction: Returns Z minima of the given geometry or null if there is no Z coordinate.
+
+Format: `ST_ZMin(geom: geometry)`
+
+Since: `v1.3.1`
+
+Spark SQL example:
+```SQL
+SELECT ST_ZMin(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'))
+```
+
+Output: `4.0`
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 9b6d634f..24c892ba 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -70,6 +70,8 @@ public class Catalog {
                 new Functions.ST_YMin(),
                 new Functions.ST_XMax(),
                 new Functions.ST_XMin(),
+                new Functions.ST_ZMax(),
+                new Functions.ST_ZMin(),
                 new Functions.ST_BuildArea(),
                 new Functions.ST_SetSRID(),
                 new Functions.ST_SRID(),
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 e92a1d6d..d3ec3f9d 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
@@ -107,6 +107,22 @@ public class Functions {
         }
     }
 
+    public static class ST_ZMax 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.zMax(geom);
+        }
+    }
+
+    public static class ST_ZMin 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.zMin(geom);
+        }
+    }
+
 
     public static class ST_Transform extends ScalarFunction {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
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 4a21efa3..95718f22 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -27,6 +27,7 @@ import org.locationtech.jts.geom.Polygon;
 import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
+import static junit.framework.TestCase.assertNull;
 import static org.apache.flink.table.api.Expressions.$;
 import static org.apache.flink.table.api.Expressions.call;
 import static org.junit.Assert.assertEquals;
@@ -169,7 +170,6 @@ public class FunctionTest extends TestBase{
         assertEquals(-0.5, result, 0);
     }
 
-
     @Test
     public void testGeomToGeoHash() {
         Table pointTable = createPointTable(testDataSize);
@@ -374,6 +374,36 @@ public class FunctionTest extends TestBase{
         assertEquals(7.89, first(pointTable).getField(0));
     }
 
+    @Test
+    public void testZMax() {
+        Table polygonTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('LINESTRING(1 3 4, 5 6 7)') AS " + polygonColNames[0]);
+        polygonTable = polygonTable.select(call(Functions.ST_ZMax.class.getSimpleName(), $(polygonColNames[0])));
+        double result = (double) first(polygonTable).getField(0);
+        assertEquals(7.0, result, 0);
+    }
+
+    @Test
+    public void testZMaxWithNoZCoordinate() {
+        Table polygonTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('LINESTRING(1 3, 5 6)') AS " + polygonColNames[0]);
+        polygonTable = polygonTable.select(call(Functions.ST_ZMax.class.getSimpleName(), $(polygonColNames[0])));
+        assertNull(first(polygonTable).getField(0));
+    }
+
+    @Test
+    public void testZMin() {
+        Table polygonTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('LINESTRING(1 3 4, 5 6 7)') AS " + polygonColNames[0]);
+        polygonTable = polygonTable.select(call(Functions.ST_ZMin.class.getSimpleName(), $(polygonColNames[0])));
+        double result = (double) first(polygonTable).getField(0);
+        assertEquals(4.0, result, 0);
+    }
+
+    @Test
+    public void testZMinWithNoZCoordinate() {
+        Table polygonTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('LINESTRING(1 3, 5 6)') AS " + polygonColNames[0]);
+        polygonTable = polygonTable.select(call(Functions.ST_ZMin.class.getSimpleName(), $(polygonColNames[0])));
+        assertNull(first(polygonTable).getField(0));
+    }
+
     @Test
     public void testXMax() {
         Table polygonTable = createPolygonTable(1);
diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py
index 323a9e0a..ae4c8daf 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -97,6 +97,8 @@ __all__ = [
     "ST_YMax",
     "ST_YMin",
     "ST_Z",
+    "ST_ZMax",
+    "ST_ZMin",
 ]
 
 
@@ -1070,3 +1072,25 @@ def ST_Z(point: ColumnOrName) -> Column:
     :rtype: Column
     """
     return _call_st_function("ST_Z", point)
+
+@validate_argument_types
+def ST_ZMax(geometry: ColumnOrName) -> Column:
+    """Return the maximum Z coordinate of a geometry.
+
+    :param geometry: Geometry column to get the maximum Z coordinate from.
+    :type geometry: ColumnOrName
+    :return: Maximum Z coordinate for the geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_ZMax", geometry)
+
+@validate_argument_types
+def ST_ZMin(geometry: ColumnOrName) -> Column:
+    """Return the minimum Z coordinate of a geometry.
+
+    :param geometry: Geometry column to get the minimum Z coordinate from.
+    :type geometry: ColumnOrName
+    :return: Minimum Z coordinate for the geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_ZMin", geometry)
\ No newline at end of file
diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py
index fdac9c68..275bfcf6 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -417,6 +417,16 @@ class TestPredicateJoin(TestBase):
 
         assert(not polygons.count())
 
+    def test_st_z_max(self):
+        linestring_df = self.spark.sql("SELECT ST_GeomFromWKT('LINESTRING Z (0 0 1, 0 1 2)') as geom")
+        linestring_row = [lnstr_row[0] for lnstr_row in linestring_df.selectExpr("ST_ZMax(geom)").collect()]
+        assert(linestring_row == [2.0])
+
+    def test_st_z_min(self):
+        linestring_df = self.spark.sql("SELECT ST_GeomFromWKT('POLYGON Z ((0 0 2, 0 1 1, 1 1 2, 1 0 2, 0 0 2))') as geom")
+        linestring_row = [lnstr_row[0] for lnstr_row in linestring_df.selectExpr("ST_ZMin(geom)").collect()]
+        assert(linestring_row == [1.0])
+
     def test_st_start_point(self):
 
         point_df = create_sample_points_df(self.spark, 5)
diff --git a/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala b/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 66c0ae70..11a1fe04 100644
--- a/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -124,6 +124,8 @@ object Catalog {
     function[ST_PointN](),
     function[ST_AsEWKT](),
     function[ST_Force_2D](),
+    function[ST_ZMax](),
+    function[ST_ZMin](),
     function[ST_YMax](),
     function[ST_YMin](),
     function[ST_XMax](),
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 298174c2..fb0875ea 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -64,6 +64,32 @@ case class ST_YMin(inputExpressions: Seq[Expression])
   }
 }
 
+/**
+  * Return the Z maxima of the geometry.
+  *
+  * @param inputExpressions This function takes a geometry and returns the maximum of all Z-coordinate values.
+*/
+case class ST_ZMax(inputExpressions: Seq[Expression])
+  extends InferredUnaryExpression(Functions.zMax) with FoldableExpression {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
+
+/**
+ * Return the Z minima of the geometry.
+ *
+ * @param inputExpressions This function takes a geometry and returns the minimum of all Z-coordinate values.
+*/
+case class ST_ZMin(inputExpressions: Seq[Expression])
+  extends InferredUnaryExpression(Functions.zMin) with FoldableExpression {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
+
 case class ST_3DDistance(inputExpressions: Seq[Expression])
   extends InferredBinaryExpression(Functions.distance3d) with FoldableExpression {
 
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
index 22928187..694f067c 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -254,4 +254,10 @@ object st_functions extends DataFrameAPI {
 
   def ST_Z(point: Column): Column = wrapExpression[ST_Z](point)
   def ST_Z(point: String): Column = wrapExpression[ST_Z](point)
+
+  def ST_ZMax(geometry: Column): Column = wrapExpression[ST_ZMax](geometry)
+  def ST_ZMax(geometry: String): Column = wrapExpression[ST_ZMax](geometry)
+
+  def ST_ZMin(geometry: Column): Column = wrapExpression[ST_ZMin](geometry)
+  def ST_ZMin(geometry: String): Column = wrapExpression[ST_ZMin](geometry)
 }
diff --git a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index a0cd4a33..02c092cd 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -82,6 +82,25 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
       assert(test.take(1)(0).get(0).asInstanceOf[Double] == -3.0)
     }
 
+    it("Passed ST_ZMax") {
+      val test = sparkSession.sql("SELECT ST_ZMax(ST_GeomFromWKT('POLYGON((0 0 0,0 5 0,5 0 0,0 0 5),(1 1 0,3 1 0,1 3 0,1 1 0))'))")
+      assert(test.take(1)(0).get(0).asInstanceOf[Double] == 5.0)
+    }
+    it("Passed ST_ZMax with no Z coordinate") {
+      val test = sparkSession.sql("SELECT ST_ZMax(ST_GeomFromWKT('POLYGON((0 0,0 5,5 0,0 0),(1 1,3 1,1 3,1 1))'))")
+      assert(test.take(1)(0).get(0) == null)
+    }
+
+    it("Passed ST_ZMin") {
+      val test = sparkSession.sql("SELECT ST_ZMin(ST_GeomFromWKT('LINESTRING(1 3 4, 5 6 7)'))")
+      assert(test.take(1)(0).get(0).asInstanceOf[Double] == 4.0)
+    }
+
+    it("Passed ST_ZMin with no Z coordinate") {
+      val test = sparkSession.sql("SELECT ST_ZMin(ST_GeomFromWKT('LINESTRING(1 3, 5 6)'))")
+      assert(test.take(1)(0).get(0) == null)
+    }
+
     it("Passed ST_Centroid") {
       var polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation)
       polygonWktDf.createOrReplaceTempView("polygontable")