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/12/22 07:16:16 UTC
[incubator-sedona] branch master updated: [SEDONA-215] Add ST_ConcaveHull (#737)
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 f6cd274f [SEDONA-215] Add ST_ConcaveHull (#737)
f6cd274f is described below
commit f6cd274f91fa4b870c1e346c521aa8bde04b472d
Author: awadhesh singh <da...@gmail.com>
AuthorDate: Thu Dec 22 12:46:10 2022 +0530
[SEDONA-215] Add ST_ConcaveHull (#737)
---
.../java/org/apache/sedona/common/Functions.java | 9 +++++++++
docs/api/flink/Function.md | 21 +++++++++++++++++++++
docs/api/sql/Function.md | 16 ++++++++++++++++
.../main/java/org/apache/sedona/flink/Catalog.java | 1 +
.../apache/sedona/flink/expressions/Functions.java | 16 ++++++++++++++++
.../java/org/apache/sedona/flink/FunctionTest.java | 13 +++++++++++++
python/sedona/sql/st_functions.py | 19 ++++++++++++++++++-
python/tests/sql/test_dataframe_api.py | 3 +++
python/tests/sql/test_function.py | 16 ++++++++++++++++
.../streaming/spark/test_constructor_functions.py | 5 +++++
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 +
.../sql/sedona_sql/expressions/Functions.scala | 13 +++++++++++++
.../sql/sedona_sql/expressions/st_functions.scala | 5 +++++
.../apache/sedona/sql/dataFrameAPITestScala.scala | 10 +++++++---
.../org/apache/sedona/sql/functionTestScala.scala | 13 +++++++++++++
15 files changed, 157 insertions(+), 4 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 af83d434..f6e24e55 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -19,6 +19,7 @@ import org.apache.sedona.common.utils.GeometryGeoHashEncoder;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.locationtech.jts.algorithm.MinimumBoundingCircle;
+import org.locationtech.jts.algorithm.hull.ConcaveHull;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
@@ -435,6 +436,14 @@ public class Functions {
return GEOMETRY_FACTORY.createLineString(coordinates.toArray(new Coordinate[0]));
}
+ public static Geometry concaveHull(Geometry geometry, double pctConvex, boolean allowHoles){
+ ConcaveHull concave_hull = new ConcaveHull(geometry);
+ concave_hull.setMaximumEdgeLengthRatio(pctConvex);
+ concave_hull.setHolesAllowed(allowHoles);
+
+ return concave_hull.getHull();
+ }
+
public static Geometry convexHull(Geometry geometry) {
return geometry.convexHull();
}
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 5acc7c14..55cc9b6e 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -223,6 +223,27 @@ Input: `MULTILINESTRING((0 0, 10 0, 10 10, 0 10, 0 0),(10 10, 20 10, 20 20, 10 2
Output: `MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0)),((10 10,10 20,20 20,20 10,10 10)))`
+## ST_ConcaveHull
+
+Introduction: Return the Concave Hull of polgyon A, with alpha set to pctConvex[0, 1] in the Delaunay Triangulation method, the concave hull will not contain a hole unless allowHoles is set to true
+
+Format: `ST_ConcaveHull (A:geometry, pctConvex:float)`
+
+Format: `ST_ConcaveHull (A:geometry, pctConvex:float, allowHoles:Boolean)`
+
+Since: `v1.3.2`
+
+Example:
+
+```SQL
+SELECT ST_ConcaveHull(polygondf.countyshape, pctConvex)`
+FROM polygondf
+```
+
+Input: `Polygon ((0 0, 1 2, 2 2, 3 2, 5 0, 4 0, 3 1, 2 1, 1 0, 0 0))`
+
+Output: `POLYGON ((1 2, 2 2, 3 2, 5 0, 4 0, 1 0, 0 0, 1 2))`
+
## ST_Distance
Introduction: Return the Euclidean distance between A and B
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 37e41681..82a104b9 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -332,6 +332,22 @@ Result:
+----------------------------------------------------------------------------+
```
+## ST_ConcaveHull
+
+Introduction: Return the Concave Hull of polgyon A, with alpha set to pctConvex[0, 1] in the Delaunay Triangulation method, the concave hull will not contain a hole unless allowHoles is set to true
+
+Format: `ST_ConcaveHull (A:geometry, pctConvex:float)`
+
+Format: `ST_ConcaveHull (A:geometry, pctConvex:float, allowHoles:Boolean)`
+
+Since: `v1.3.2`
+
+Spark SQL example:
+```SQL
+SELECT ST_ConcaveHull(polygondf.countyshape, pctConvex)`
+FROM polygondf
+```
+
## ST_ConvexHull
Introduction: Return the Convex Hull of polgyon A
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 40c9e450..96f6b4ee 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -40,6 +40,7 @@ public class Catalog {
new Functions.ST_Azimuth(),
new Functions.ST_Boundary(),
new Functions.ST_Buffer(),
+ new Functions.ST_ConcaveHull(),
new Functions.ST_Envelope(),
new Functions.ST_Distance(),
new Functions.ST_3DDistance(),
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 6d046f4c..c104e9e0 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
@@ -55,6 +55,22 @@ public class Functions {
}
}
+ public static class ST_ConcaveHull 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,
+ @DataTypeHint("Double") Double pctConvex) {
+ Geometry geom = (Geometry) o;
+ return org.apache.sedona.common.Functions.concaveHull(geom, pctConvex, false);
+ }
+
+ @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,
+ @DataTypeHint("Double") Double pctConvex, @DataTypeHint("Boolean") Boolean allowHoles) {
+ Geometry geom = (Geometry) o;
+ return org.apache.sedona.common.Functions.concaveHull(geom, pctConvex, allowHoles);
+ }
+ }
+
public static class ST_Envelope 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 2c212231..cf04865d 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -72,6 +72,19 @@ public class FunctionTest extends TestBase{
assert(result instanceof Polygon);
}
+ @Test
+ public void testConcaveHull() {
+ Table polygonTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('Polygon ((0 0, 1 2, 2 2, 3 2, 5 0, 4 0, 3 1, 2 1, 1 0, 0 0))') as geom");
+ Table concaveHullPolygonTable = polygonTable.select(call(Functions.ST_ConcaveHull.class.getSimpleName(), $("geom"), 1.0, true));
+ Geometry result = (Geometry) first(concaveHullPolygonTable).getField(0);
+ assertEquals("POLYGON ((1 2, 2 2, 3 2, 5 0, 4 0, 1 0, 0 0, 1 2))", result.toString());
+
+ Table polygonTable2 = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') as geom");
+ Table concaveHullPolygonTable2 = polygonTable2.select(call(Functions.ST_ConcaveHull.class.getSimpleName(), $("geom"), 1.0));
+ Geometry result2 = (Geometry) first(concaveHullPolygonTable2).getField(0);
+ assertEquals("POLYGON ((0 0, 1 1, 1 0, 0 0))", result2.toString());
+ }
+
@Test
public void testEnvelope() {
Table linestringTable = createLineStringTable(1);
diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py
index 4de6e050..a33b76bd 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -41,6 +41,7 @@ __all__ = [
"ST_Centroid",
"ST_Collect",
"ST_CollectionExtract",
+ "ST_ConcaveHull",
"ST_ConvexHull",
"ST_Difference",
"ST_Distance",
@@ -334,6 +335,22 @@ def ST_CollectionExtract(collection: ColumnOrName, geom_type: Optional[Union[Col
return _call_st_function("ST_CollectionExtract", args)
+@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.
+
+ :param geometry: Geometry column to generate a cancave hull for.
+ :type geometry: ColumnOrName
+ :param pctConvex: value between 0 and 1, controls the concaveness of the computed hull.
+ :type pctConvex: Union[ColumnOrName, float]
+ :param allowHoles: The computed hull will not contain holes unless allowHoles is specified as true
+ :type allowHoles: Optional[Union[ColumnOrName, bool]], optional
+ :return: Concave hull of geometry as a geometry column.
+ :rtype: Column
+ """
+ args = (geometry, pctConvex) if allowHoles is None else (geometry, pctConvex, allowHoles)
+ return _call_st_function("ST_ConcaveHull", args)
+
@validate_argument_types
def ST_ConvexHull(geometry: ColumnOrName) -> Column:
"""Generate the convex hull of a geometry column.
@@ -1106,4 +1123,4 @@ def ST_ZMin(geometry: ColumnOrName) -> Column:
: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
+ return _call_st_function("ST_ZMin", geometry)
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
index 341ac8ce..bef6634b 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -69,6 +69,8 @@ test_configurations = [
(stf.ST_Collect, ("a", "b"), "two_points", "", "MULTIPOINT Z (0 0 0, 3 0 4)"),
(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))"),
+ (stf.ST_ConcaveHull, ("geom", 1.0, True), "triangle_geom", "", "POLYGON ((1 1, 1 0, 0 0, 1 1))"),
(stf.ST_ConvexHull, ("geom",), "triangle_geom", "", "POLYGON ((0 0, 1 1, 1 0, 0 0))"),
(stf.ST_Difference, ("a", "b"), "overlapping_polys", "", "POLYGON ((1 0, 0 0, 0 1, 1 1, 1 0))"),
(stf.ST_Distance, ("a", "b"), "two_points", "", 3.0),
@@ -196,6 +198,7 @@ wrong_type_configurations = [
(stf.ST_Centroid, (None,)),
(stf.ST_Collect, (None,)),
(stf.ST_CollectionExtract, (None,)),
+ (stf.ST_ConcaveHull, (None, 1.0)),
(stf.ST_ConvexHull, (None,)),
(stf.ST_Difference, (None, "b")),
(stf.ST_Difference, ("", None)),
diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py
index 5fba932d..8db02262 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -50,6 +50,22 @@ class TestPredicateJoin(TestBase):
]
)
+ def test_st_concave_hull(self):
+ polygon_wkt_df = self.spark.read.format("csv"). \
+ option("delimiter", "\t"). \
+ option("header", "false"). \
+ load(mixed_wkt_geometry_input_location)
+
+ polygon_wkt_df.createOrReplaceTempView("polygontable")
+ polygon_wkt_df.show()
+
+ polygon_df = self.spark.sql("select ST_GeomFromWKT(polygontable._c0) as countyshape from polygontable")
+ polygon_df.createOrReplaceTempView("polygondf")
+ polygon_df.show()
+
+ function_df = self.spark.sql("select ST_ConcaveHull(polygondf.countyshape, 1.0) from polygondf")
+ function_df.show()
+
def test_st_convex_hull(self):
polygon_wkt_df = self.spark.read.format("csv"). \
option("delimiter", "\t"). \
diff --git a/python/tests/streaming/spark/test_constructor_functions.py b/python/tests/streaming/spark/test_constructor_functions.py
index c6f7830a..6fc55b99 100644
--- a/python/tests/streaming/spark/test_constructor_functions.py
+++ b/python/tests/streaming/spark/test_constructor_functions.py
@@ -49,6 +49,11 @@ SEDONA_LISTED_SQL_FUNCTIONS = [
.with_function_name("ST_Distance")
.with_arguments(["ST_GeomFromText('POINT (21 52)')", "ST_GeomFromText('POINT (21 53)')"])
.with_expected_result(1.0)),
+ (SuiteContainer.empty()
+ .with_function_name("ST_ConcaveHull")
+ .with_arguments(["ST_GeomFromText('POLYGON ((21 52, 21 53, 22 53, 22 52, 21 52))')", "1.0"])
+ .with_expected_result(1.0)
+ .with_transform("ST_AREA")),
(SuiteContainer.empty()
.with_function_name("ST_ConvexHull")
.with_arguments(["ST_GeomFromText('POLYGON ((21 52, 21 53, 22 53, 22 52, 21 52))')"])
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 09e1d256..3f467aec 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
@@ -56,6 +56,7 @@ object Catalog {
function[ST_Disjoint](),
function[ST_Distance](),
function[ST_3DDistance](),
+ function[ST_ConcaveHull](false),
function[ST_ConvexHull](),
function[ST_NPoints](),
function[ST_NDims](),
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 c7435f93..5e5ad11b 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
@@ -98,6 +98,19 @@ case class ST_3DDistance(inputExpressions: Seq[Expression])
}
}
+/**
+ * Return the concave hull of a Geometry.
+ *
+ * @param inputExpressions
+ */
+case class ST_ConcaveHull(inputExpressions: Seq[Expression])
+ extends InferredTernaryExpression(Functions.concaveHull) with FoldableExpression {
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): Expression = {
+ copy(inputExpressions = newChildren)
+ }
+}
+
/**
* Return the convex hull of a Geometry.
*
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 90de477c..16073e93 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
@@ -79,6 +79,11 @@ object st_functions extends DataFrameAPI {
def ST_CollectionExtract(collection: Column, geomType: Column): Column = wrapExpression[ST_CollectionExtract](collection, geomType)
def ST_CollectionExtract(collection: String, geomType: Int): Column = wrapExpression[ST_CollectionExtract](collection, geomType)
+ def ST_ConcaveHull(geometry: Column, pctConvex:Column): Column = wrapExpression[ST_ConcaveHull](geometry, pctConvex, false)
+ def ST_ConcaveHull(geometry: String, pctConvex:Double): Column = wrapExpression[ST_ConcaveHull](geometry, pctConvex, false)
+ def ST_ConcaveHull(geometry: Column, pctConvex:Column, allowHoles: Column): Column = wrapExpression[ST_ConcaveHull](geometry, pctConvex, allowHoles)
+ def ST_ConcaveHull(geometry: String, pctConvex:Double, allowHoles: Boolean): Column = wrapExpression[ST_ConcaveHull](geometry, pctConvex, allowHoles)
+
def ST_ConvexHull(geometry: Column): Column = wrapExpression[ST_ConvexHull](geometry)
def ST_ConvexHull(geometry: String): Column = wrapExpression[ST_ConvexHull](geometry)
diff --git a/sql/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala b/sql/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
index 47790e55..7d2deca7 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -19,12 +19,9 @@
package org.apache.sedona.sql
import scala.collection.mutable.WrappedArray
-
import org.apache.commons.codec.binary.Hex
import org.apache.spark.sql.functions.lit
-
import org.locationtech.jts.geom.Geometry
-
import org.apache.spark.sql.sedona_sql.expressions.st_constructors._
import org.apache.spark.sql.sedona_sql.expressions.st_functions._
import org.apache.spark.sql.sedona_sql.expressions.st_predicates._
@@ -153,6 +150,13 @@ class dataFrameAPITestScala extends TestBaseScala {
}
// functions
+ it("Passed ST_ConcaveHull"){
+ val baseDF = sparkSession.sql("SELECT ST_GeomFromWKT('Polygon ((0 0, 1 2, 2 2, 3 2, 5 0, 4 0, 3 1, 2 1, 1 0, 0 0))') as mline")
+ val df = baseDF.select(ST_ConcaveHull("mline", 1, true))
+ val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+ assert(actualResult == "POLYGON ((1 2, 2 2, 3 2, 5 0, 4 0, 1 0, 0 0, 1 2))")
+ }
+
it("Passed ST_ConvexHull") {
val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
val df = polygonDf.select(ST_ConvexHull("geom"))
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 9562b1e2..784a2200 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -43,6 +43,15 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
describe("Sedona-SQL Function Test") {
+ it("Passed ST_ConcaveHull") {
+ var polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation)
+ polygonWktDf.createOrReplaceTempView("polygontable")
+ var polygonDf = sparkSession.sql("select ST_GeomFromWKT(polygontable._c0) as countyshape from polygontable")
+ polygonDf.createOrReplaceTempView("polygondf")
+ var functionDf = sparkSession.sql("select ST_ConcaveHull(polygondf.countyshape, 1, true) from polygondf")
+ assert(functionDf.count() > 0);
+ }
+
it("Passed ST_ConvexHull") {
var polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation)
polygonWktDf.createOrReplaceTempView("polygontable")
@@ -1625,6 +1634,10 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
assert(functionDf.first().get(0) == null)
functionDf = sparkSession.sql("select ST_3DDistance(null, null)")
assert(functionDf.first().get(0) == null)
+ functionDf = sparkSession.sql("select ST_ConcaveHull(null, 1, true)")
+ assert(functionDf.first().get(0) == null)
+ functionDf = sparkSession.sql("select ST_ConcaveHull(null, 1)")
+ assert(functionDf.first().get(0) == null)
functionDf = sparkSession.sql("select ST_ConvexHull(null)")
assert(functionDf.first().get(0) == null)
functionDf = sparkSession.sql("select ST_NPoints(null)")