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/10/09 06:56:50 UTC
[incubator-sedona] branch master updated: [SEDONA-171] Add ST_SetPoint to Apache Sedona (#694)
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 206936a0 [SEDONA-171] Add ST_SetPoint to Apache Sedona (#694)
206936a0 is described below
commit 206936a04fe9fdb153bab71ad330e062711421ab
Author: Kengo Seki <se...@apache.org>
AuthorDate: Sun Oct 9 15:56:43 2022 +0900
[SEDONA-171] Add ST_SetPoint to Apache Sedona (#694)
---
.../java/org/apache/sedona/common/Functions.java | 15 +++++++++
docs/api/flink/Function.md | 24 ++++++++++++++
docs/api/sql/Function.md | 24 ++++++++++++++
.../main/java/org/apache/sedona/flink/Catalog.java | 1 +
.../apache/sedona/flink/expressions/Functions.java | 10 ++++++
.../java/org/apache/sedona/flink/FunctionTest.java | 14 ++++++--
python/sedona/sql/st_functions.py | 17 ++++++++++
python/tests/sql/test_dataframe_api.py | 4 +++
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 +
.../sql/sedona_sql/expressions/Functions.scala | 8 +++++
.../expressions/NullSafeExpressions.scala | 37 ++++++++++++++++++++++
.../sql/sedona_sql/expressions/st_functions.scala | 3 ++
.../apache/sedona/sql/dataFrameAPITestScala.scala | 8 +++++
.../org/apache/sedona/sql/functionTestScala.scala | 27 ++++++++++++++++
14 files changed, 190 insertions(+), 3 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 b8c712fe..aeb8abbc 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -335,6 +335,21 @@ public class Functions {
return null;
}
+ public static Geometry setPoint(Geometry linestring, int position, Geometry point) {
+ if (linestring instanceof LineString) {
+ List<Coordinate> coordinates = new ArrayList<>(Arrays.asList(linestring.getCoordinates()));
+ if (-coordinates.size() <= position && position < coordinates.size()) {
+ if (position < 0) {
+ coordinates.set(coordinates.size() + position, point.getCoordinate());
+ } else {
+ coordinates.set(position, point.getCoordinate());
+ }
+ return GEOMETRY_FACTORY.createLineString(coordinates.toArray(new Coordinate[0]));
+ }
+ }
+ return null;
+ }
+
public static Geometry lineFromMultiPoint(Geometry geometry) {
if(!(geometry instanceof MultiPoint)) {
return null;
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index c908a2f3..308a0556 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -636,6 +636,30 @@ SELECT ST_RemovePoint(ST_GeomFromText("LINESTRING(0 0, 1 1, 1 0)"), 1)
Output: `LINESTRING(0 0, 1 0)`
+## ST_SetPoint
+
+Introduction: Replace Nth point of linestring with given point. Index is 0-based. Negative index are counted backwards, e.g., -1 is last point.
+
+Format: `ST_SetPoint (linestring: geometry, index: integer, point: geometry)`
+
+Since: `v1.3.0`
+
+Example:
+
+```SQL
+SELECT ST_SetPoint(ST_GeomFromText('LINESTRING (0 0, 0 1, 1 1)'), 2, ST_GeomFromText('POINT (1 0)')) AS geom
+```
+
+Result:
+
+```
++--------------------------------+
+| geom |
++--------------------------------+
+| LINESTRING (0 0, 0 1, 1 0) |
++--------------------------------+
+```
+
## ST_SetSRID
Introduction: Sets the spatial refence system identifier (SRID) of the geometry.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 1baacdcd..6a55b969 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -1063,6 +1063,30 @@ Result:
+---------------------------------------------------------------+
```
+## ST_SetPoint
+
+Introduction: Replace Nth point of linestring with given point. Index is 0-based. Negative index are counted backwards, e.g., -1 is last point.
+
+Format: `ST_SetPoint (linestring: geometry, index: integer, point: geometry)`
+
+Since: `v1.3.0`
+
+Example:
+
+```SQL
+SELECT ST_SetPoint(ST_GeomFromText('LINESTRING (0 0, 0 1, 1 1)'), 2, ST_GeomFromText('POINT (1 0)')) AS geom
+```
+
+Result:
+
+```
++--------------------------+
+|geom |
++--------------------------+
+|LINESTRING (0 0, 0 1, 1 0)|
++--------------------------+
+```
+
## ST_SetSRID
Introduction: Sets the spatial refence system identifier (SRID) of the geometry.
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 a6a20a27..289cfe38 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -80,6 +80,7 @@ public class Catalog {
new Functions.ST_Normalize(),
new Functions.ST_AddPoint(),
new Functions.ST_RemovePoint(),
+ new Functions.ST_SetPoint(),
new Functions.ST_LineFromMultiPoint(),
};
}
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 de364772..1232f3cc 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
@@ -414,6 +414,16 @@ public class Functions {
}
}
+ public static class ST_SetPoint 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, int position,
+ @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o2) {
+ Geometry linestring = (Geometry) o1;
+ Geometry point = (Geometry) o2;
+ return org.apache.sedona.common.Functions.setPoint(linestring, position, point);
+ }
+ }
+
public static class ST_LineFromMultiPoint 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 ba205ff9..1afeb47d 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -435,28 +435,36 @@ public class FunctionTest extends TestBase{
public void testAddPoint() {
Table pointTable = tableEnv.sqlQuery("SELECT ST_AddPoint(ST_GeomFromWKT('LINESTRING (0 0, 1 1)'), ST_GeomFromWKT('POINT (2 2)'))");
assertEquals("LINESTRING (0 0, 1 1, 2 2)", first(pointTable).getField(0).toString());
-
}
@Test
public void testAddPointWithIndex() {
Table pointTable = tableEnv.sqlQuery("SELECT ST_AddPoint(ST_GeomFromWKT('LINESTRING (0 0, 1 1)'), ST_GeomFromWKT('POINT (2 2)'), 1)");
assertEquals("LINESTRING (0 0, 2 2, 1 1)", first(pointTable).getField(0).toString());
-
}
@Test
public void testRemovePoint() {
Table pointTable = tableEnv.sqlQuery("SELECT ST_RemovePoint(ST_GeomFromWKT('LINESTRING (0 0, 1 1, 2 2)'))");
assertEquals("LINESTRING (0 0, 1 1)", first(pointTable).getField(0).toString());
-
}
@Test
public void testRemovePointWithIndex() {
Table pointTable = tableEnv.sqlQuery("SELECT ST_RemovePoint(ST_GeomFromWKT('LINESTRING (0 0, 1 1, 2 2)'), 1)");
assertEquals("LINESTRING (0 0, 2 2)", first(pointTable).getField(0).toString());
+ }
+ @Test
+ public void testSetPoint() {
+ Table pointTable = tableEnv.sqlQuery("SELECT ST_SetPoint(ST_GeomFromWKT('LINESTRING (0 0, 1 1, 2 2)'), 0, ST_GeomFromWKT('POINT (3 3)'))");
+ assertEquals("LINESTRING (3 3, 1 1, 2 2)", first(pointTable).getField(0).toString());
+ }
+
+ @Test
+ public void testSetPointWithNegativeIndex() {
+ Table pointTable = tableEnv.sqlQuery("SELECT ST_SetPoint(ST_GeomFromWKT('LINESTRING (0 0, 1 1, 2 2)'), -1, ST_GeomFromWKT('POINT (3 3)'))");
+ assertEquals("LINESTRING (0 0, 1 1, 3 3)", first(pointTable).getField(0).toString());
}
@Test
diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py
index ed611efd..d8bbfbcb 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -63,6 +63,7 @@ __all__ = [
"ST_PrecisionReduce",
"ST_RemovePoint",
"ST_Reverse",
+ "ST_SetPoint",
"ST_SetSRID",
"ST_SRID",
"ST_StartPoint",
@@ -827,6 +828,22 @@ def ST_Reverse(geometry: ColumnOrName) -> Column:
return _call_st_function("ST_Reverse", geometry)
+@validate_argument_types
+def ST_SetPoint(line_string: ColumnOrName, index: Union[ColumnOrName, int], point: ColumnOrName) -> Column:
+ """Replace a point in a linestring.
+
+ :param line_string: Linestring geometry column which contains the point to be replaced.
+ :type line_string: ColumnOrName
+ :param index: Index for the point to be replaced, 0-based, negative values start from the end so -1 is the last point.
+ :type index: Union[ColumnOrName, int]
+ :param point: Point geometry column to be newly set.
+ :type point: ColumnOrName
+ :return: Linestring geometry column with the replaced point, or null if the index is out of bounds.
+ :rtype: Column
+ """
+ return _call_st_function("ST_SetPoint", (line_string, index, point))
+
+
@validate_argument_types
def ST_SetSRID(geometry: ColumnOrName, srid: Union[ColumnOrName, int]) -> Column:
"""Set the SRID for geometry.
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
index 2b91b636..e072e3e2 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -91,6 +91,7 @@ test_configurations = [
(stf.ST_PrecisionReduce, ("geom", 1), "precision_reduce_point", "", "POINT (0.1 0.2)"),
(stf.ST_RemovePoint, ("line", 1), "linestring_geom", "", "LINESTRING (0 0, 2 0, 3 0, 4 0, 5 0)"),
(stf.ST_Reverse, ("line",), "linestring_geom", "", "LINESTRING (5 0, 4 0, 3 0, 2 0, 1 0, 0 0)"),
+ (stf.ST_SetPoint, ("line", 1, lambda: f.expr("ST_Point(1.0, 1.0)")), "linestring_geom", "", "LINESTRING (0 0, 1 1, 2 0, 3 0, 4 0, 5 0)"),
(stf.ST_SetSRID, ("point", 3021), "point_geom", "ST_SRID(geom)", 3021),
(stf.ST_SimplifyPreserveTopology, ("geom", 0.2), "0.9_poly", "", "POLYGON ((0 0, 1 0, 1 1, 0 0))"),
(stf.ST_SRID, ("point",), "point_geom", "", 0),
@@ -226,6 +227,9 @@ wrong_type_configurations = [
(stf.ST_RemovePoint, ("", None)),
(stf.ST_RemovePoint, ("", 1.0)),
(stf.ST_Reverse, (None,)),
+ (stf.ST_SetPoint, (None, 1, "")),
+ (stf.ST_SetPoint, ("", None, "")),
+ (stf.ST_SetPoint, ("", 1, None)),
(stf.ST_SetSRID, (None, 3021)),
(stf.ST_SetSRID, ("", None)),
(stf.ST_SetSRID, ("", 3021.0)),
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 ebfe78dc..2f5280e6 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
@@ -97,6 +97,7 @@ object Catalog {
ST_NumInteriorRings,
ST_AddPoint,
ST_RemovePoint,
+ ST_SetPoint,
ST_IsRing,
ST_FlipCoordinates,
ST_LineSubstring,
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 658d644e..065539e0 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
@@ -892,6 +892,14 @@ case class ST_RemovePoint(inputExpressions: Seq[Expression])
}
}
+case class ST_SetPoint(inputExpressions: Seq[Expression])
+ extends InferredTernaryExpression(Functions.setPoint) {
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+ copy(inputExpressions = newChildren)
+ }
+}
+
case class ST_IsRing(inputExpressions: Seq[Expression])
extends UnaryGeometryExpression with CodegenFallback {
assert(inputExpressions.length == 1)
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/NullSafeExpressions.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/NullSafeExpressions.scala
index 8b2b1496..bb0c599a 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/NullSafeExpressions.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/NullSafeExpressions.scala
@@ -209,3 +209,40 @@ abstract class InferredBinaryExpression[A1: InferrableType, A2: InferrableType,
}
}
}
+
+abstract class InferredTernaryExpression[A1: InferrableType, A2: InferrableType, A3: InferrableType, R: InferrableType]
+(f: (A1, A2, A3) => R)
+(implicit val a1Tag: TypeTag[A1], implicit val a2Tag: TypeTag[A2], implicit val a3Tag: TypeTag[A3], implicit val rTag: TypeTag[R])
+ extends Expression with ImplicitCastInputTypes with CodegenFallback with Serializable {
+ import InferredTypes._
+
+ def inputExpressions: Seq[Expression]
+ assert(inputExpressions.length == 3)
+
+ override def children: Seq[Expression] = inputExpressions
+
+ override def toString: String = s" **${getClass.getName}** "
+
+ override def inputTypes: Seq[AbstractDataType] = Seq(inferSparkType[A1], inferSparkType[A2], inferSparkType[A3])
+
+ override def nullable: Boolean = true
+
+ override def dataType = inferSparkType[R]
+
+ lazy val extractFirst = buildExtractor[A1](inputExpressions(0))
+ lazy val extractSecond = buildExtractor[A2](inputExpressions(1))
+ lazy val extractThird = buildExtractor[A3](inputExpressions(2))
+
+ lazy val serialize = buildSerializer[R]
+
+ override def eval(input: InternalRow): Any = {
+ val first = extractFirst(input)
+ val second = extractSecond(input)
+ val third = extractThird(input)
+ if (first != null && second != null && third != null) {
+ serialize(f(first, second, third))
+ } else {
+ null
+ }
+ }
+}
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 f0562e38..4a54719f 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
@@ -202,6 +202,9 @@ object st_functions extends DataFrameAPI {
def ST_Reverse(geometry: Column): Column = wrapExpression[ST_Reverse](geometry)
def ST_Reverse(geometry: String): Column = wrapExpression[ST_Reverse](geometry)
+ def ST_SetPoint(lineString: Column, index: Column, point: Column): Column = wrapExpression[ST_SetPoint](lineString, index, point)
+ def ST_SetPoint(lineString: String, index: Int, point: String): Column = wrapExpression[ST_SetPoint](lineString, index, point)
+
def ST_SetSRID(geometry: Column, srid: Column): Column = wrapExpression[ST_SetSRID](geometry, srid)
def ST_SetSRID(geometry: String, srid: Int): Column = wrapExpression[ST_SetSRID](geometry, srid)
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 36c805f7..ce7aa0af 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -529,6 +529,14 @@ class dataFrameAPITestScala extends TestBaseScala {
assert(actualResult == expectedResult)
}
+ it("Passed ST_SetPoint") {
+ val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS line, ST_Point(1.0, 1.0) AS point")
+ val df = baseDf.select(ST_SetPoint("line", 1, "point"))
+ val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+ val expectedResult = "LINESTRING (0 0, 1 1)"
+ assert(actualResult == expectedResult)
+ }
+
it("Passed ST_IsRing") {
val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)') AS geom")
val df = baseDf.select(ST_IsRing("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 dee0b983..f642f34c 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -993,6 +993,24 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
calculateStRemovePointOption("MULTILINESTRING ((10 10, 20 20, 10 40, 10 10), (40 40, 30 30, 40 20, 30 10, 40 40))", 3) shouldBe None
}
+ it("Should correctly set using ST_SetPoint") {
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", 0, "Point(0 1)") shouldBe Some("LINESTRING (0 1, 1 1, 1 0, 0 0)")
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", 1, "Point(0 1)") shouldBe Some("LINESTRING (0 0, 0 1, 1 0, 0 0)")
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", 2, "Point(0 1)") shouldBe Some("LINESTRING (0 0, 1 1, 0 1, 0 0)")
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", 3, "Point(0 1)") shouldBe Some("LINESTRING (0 0, 1 1, 1 0, 0 1)")
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", 4, "Point(0 1)") shouldBe None
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", -1, "Point(0 1)") shouldBe Some("LINESTRING (0 0, 1 1, 1 0, 0 1)")
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", -2, "Point(0 1)") shouldBe Some("LINESTRING (0 0, 1 1, 0 1, 0 0)")
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", -3, "Point(0 1)") shouldBe Some("LINESTRING (0 0, 0 1, 1 0, 0 0)")
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", -4, "Point(0 1)") shouldBe Some("LINESTRING (0 1, 1 1, 1 0, 0 0)")
+ calculateStSetPointOption("Linestring(0 0, 1 1, 1 0, 0 0)", -5, "Point(0 1)") shouldBe None
+ calculateStSetPointOption("POINT(0 1)", 0, "Point(0 1)") shouldBe None
+ calculateStSetPointOption("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))", 0, "Point(0 1)") shouldBe None
+ calculateStSetPointOption("GEOMETRYCOLLECTION (POINT (40 10), LINESTRING (10 10, 20 20, 10 40))", 0, "Point(0 1)") shouldBe None
+ calculateStSetPointOption("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))", 0, "Point(0 1)") shouldBe None
+ calculateStSetPointOption("MULTILINESTRING ((10 10, 20 20, 10 40, 10 10), (40 40, 30 30, 40 20, 30 10, 40 40))", 0, "Point(0 1)") shouldBe None
+ }
+
it("Should pass ST_IsRing") {
calculateStIsRing("LINESTRING(0 0, 0 1, 1 0, 1 1, 0 0)") shouldBe Some(false)
calculateStIsRing("LINESTRING(2 0, 2 2, 3 3)") shouldBe Some(false)
@@ -1152,6 +1170,15 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
.filter("geom is not null")
.selectExpr("ST_AsText(geom)").as[String].collect()
+ private def calculateStSetPointOption(wktA: String, index: Int, wktB: String): Option[String] =
+ calculateStSetPoint(wktA, index, wktB).headOption
+
+ private def calculateStSetPoint(wktA: String, index: Int, wktB: String): Array[String] =
+ Seq(Tuple3(wktReader.read(wktA), index, wktReader.read(wktB))).toDF("geomA", "index", "geomB")
+ .selectExpr(s"ST_SetPoint(geomA, index, geomB) as geom")
+ .filter("geom is not null")
+ .selectExpr("ST_AsText(geom)").as[String].collect()
+
it("Passed ST_NumGeometries") {
Given("Some different types of geometries in a DF")
// Test data