This is an automated email from the ASF dual-hosted git repository. jiayu pushed a commit to branch SEDONA-602 in repository https://gitbox.apache.org/repos/asf/sedona.git
commit 8819bb15a2148b6ebc463c6df5a704d35a0b8af7 Author: Feng Zhang <[email protected]> AuthorDate: Fri May 31 09:44:06 2024 -0700 Merge pull request #204 from wherobots/st-locate-along [TASK-187] Add ST_LocateAlong --- .../java/org/apache/sedona/common/Functions.java | 8 ++ .../org/apache/sedona/common/utils/GeomUtils.java | 96 ++++++++++++++++++++++ .../org/apache/sedona/common/FunctionsTest.java | 40 +++++++++ docs/api/flink/Function.md | 30 +++++++ docs/api/sql/Function.md | 30 +++++++ .../main/java/org/apache/sedona/flink/Catalog.java | 1 + .../apache/sedona/flink/expressions/Functions.java | 17 ++++ .../java/org/apache/sedona/flink/FunctionTest.java | 14 ++++ python/sedona/sql/st_functions.py | 16 ++++ python/tests/sql/test_dataframe_api.py | 5 ++ python/tests/sql/test_function.py | 10 +++ .../sedona/snowflake/snowsql/TestFunctionsV2.java | 1 - .../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 + .../sql/sedona_sql/expressions/Functions.scala | 8 ++ .../sql/sedona_sql/expressions/st_functions.scala | 5 ++ .../apache/sedona/sql/dataFrameAPITestScala.scala | 11 +++ .../org/apache/sedona/sql/functionTestScala.scala | 11 +++ 17 files changed, 303 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 161d862db..767e1b77b 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -974,6 +974,14 @@ public class Functions { return indexedLine.indexOf(point.getCoordinate()) / length; } + public static Geometry locateAlong(Geometry linear, double measure, double offset) { + return GeomUtils.locateAlong(linear, measure, offset); + } + + public static Geometry locateAlong(Geometry linear, double measure) { + return locateAlong(linear, measure, 0); + } + /** * Forces a Polygon/MultiPolygon to use counter-clockwise orientation for the exterior ring and a clockwise for the interior ring(s). * @param geom diff --git a/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java b/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java index fc36e79e5..b946dd7fb 100644 --- a/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java +++ b/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java @@ -550,4 +550,100 @@ public class GeomUtils { return geom; } } + + public static Geometry locateAlong(Geometry geometry, double measure, double offset) { + if (!isMeasuredGeometry(geometry)) { + throw new IllegalArgumentException("Input geometry is doesn't have a measure dimension"); + } + + if (geometry instanceof Point) { + return locateAlongPoint(geometry, measure, offset); + } + if (geometry instanceof MultiPoint) { + // iterating through Points in MultiPoint object + Point[] points = new Point[geometry.getNumGeometries()]; + for (int i = 0; i < geometry.getNumGeometries(); i++) { + points[i] = locateAlongPoint(geometry.getGeometryN(i), measure, offset); + } + return geometry.getFactory().createMultiPoint(Arrays.stream(points).filter(Objects::nonNull).toArray(Point[]::new)); + } + if (geometry instanceof LineString) { + return locateAlongLinestring(geometry, measure, offset); + } + if (geometry instanceof MultiLineString) { + // iterating through LineStrings in MultiLineString object + List<Point> points = new ArrayList<>(); + for (int i = 0; i < geometry.getNumGeometries(); i++) { + MultiPoint mPoint = (MultiPoint) locateAlongLinestring(geometry.getGeometryN(i), measure, offset); + for (int j = 0; j < mPoint.getNumGeometries(); j++) { + points.add((Point) mPoint.getGeometryN(j)); + } + } + + return geometry.getFactory().createMultiPoint(points.toArray(new Point[0])); + } + + throw new IllegalArgumentException(String.format("%s geometry type not supported, supported types are: (Multi)Point and (Multi)LineString.", geometry.getGeometryType())); + } + + private static Geometry locateAlongLinestring(Geometry geometry, double measure, double offset) { + Coordinate[] coordinates = geometry.getCoordinates(); + CoordinateList coordinateList = new CoordinateList(); + + for (int i = 1; i < coordinates.length; i++) { + Coordinate coordinate1 = coordinates[i - 1]; + Coordinate coordinate2 = coordinates[i]; + CoordinateXYZM newCoordinate = new CoordinateXYZM(); + double position; + + double measure1 = coordinate1.getM(), + measure2 = coordinate2.getM(); + + if ((measure < Math.min(measure1, measure2)) || (measure > Math.max(measure1, measure2))) { + continue; + } + + if (measure1 == measure2) { + // If the measures are equal then there is no valid interpolation range + if (coordinate1.equals(coordinate2)) { + newCoordinate.setX(coordinate1.getX()); + newCoordinate.setY(coordinate1.getY()); + newCoordinate.setZ(coordinate1.getZ()); + newCoordinate.setM(coordinate1.getM()); + coordinateList.add(newCoordinate, false); + continue; + } + // the point will be in the midpoint of coordinate1 and coordinate2 as measure1 and measure2 are same + position = 0.5; + } else { + // calculate the interpolation factor / position + position = (measure - measure1) / (measure2 - measure1); + } + + // apply linear interpolation to find the point along the line + newCoordinate.setX(coordinate1.x + (coordinate2.x - coordinate1.x) * position); + newCoordinate.setY(coordinate1.y + (coordinate2.y - coordinate1.y) * position); + newCoordinate.setZ(coordinate1.z + (coordinate2.z - coordinate1.z) * position); + newCoordinate.setM(measure); + + if (offset != 0D) { + // calculate the angle of the line segment + double theta = Math.atan2(coordinate2.y - coordinate1.y, coordinate2.x - coordinate1.x); + // shift the coordinate left or right by the offset + // if the offset is positive then shift to left + // else the offset is negative then shift to right + newCoordinate.setX(newCoordinate.x - Math.sin(theta) * offset); + newCoordinate.setY(newCoordinate.y + Math.cos(theta) * offset); + } + coordinateList.add(newCoordinate, false); + } + return geometry.getFactory().createMultiPointFromCoords(Arrays.stream(coordinateList.toCoordinateArray()).filter(Objects::nonNull).toArray(Coordinate[]::new)); + } + + private static Point locateAlongPoint(Geometry geometry, double measure, double offset) { + if (measure == geometry.getCoordinate().getM()) { + return (Point) geometry; + } + return null; + } } 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 8915c2cab..81b73db32 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -2703,6 +2703,46 @@ public class FunctionsTest extends TestBase { assertEquals(expectedResult3, actual3, FP_TOLERANCE); } + @Test + public void locateAlong() throws ParseException { + Geometry geom = Constructors.geomFromEWKT("MULTIPOINT M(1 2 3, 3 4 3, 9 4 3, 3 2 1, 1 2 3, 5 4 2)"); + String actual = Functions.asWKT(Functions.locateAlong(geom, 3, 0)); + String expected = "MULTIPOINT M((1 2 3), (3 4 3), (9 4 3), (1 2 3))"; + assertEquals(expected, actual); + + // offset doesn't affect Point or MultiPoint + actual = Functions.asWKT(Functions.locateAlong(geom, 3, 4)); + assertEquals(expected, actual); + + geom = Constructors.geomFromEWKT("LINESTRING M(1 2 3, 3 4 3, 5 4 2, 9 4 3)"); + actual = Functions.asWKT(Functions.locateAlong(geom, 3, 0)); + expected = "MULTIPOINT M((2 3 3), (3 4 3), (9 4 3))"; + assertEquals(expected, actual); + + actual = Functions.asWKT(Functions.locateAlong(geom, 3, 2)); + expected = "MULTIPOINT M((0.5857864376269051 4.414213562373095 3), (3 6 3), (9 6 3))"; + assertEquals(expected, actual); + + geom = Constructors.geomFromEWKT("LINESTRING M(1 2 3, 3 4 3, 5 4 2, 5 4 2, 9 4 3)"); + actual = Functions.asWKT(Functions.locateAlong(geom, 2, 0)); + expected = "MULTIPOINT M((5 4 2))"; + assertEquals(expected, actual); + + geom = Constructors.geomFromEWKT("MULTILINESTRING M((1 2 3, 3 4 2, 9 4 3),(1 2 3, 5 4 5))"); + actual = Functions.asWKT(Functions.locateAlong(geom, 2, 0)); + expected = "MULTIPOINT M((3 4 2))"; + assertEquals(expected, actual); + + actual = Functions.asWKT(Functions.locateAlong(geom, 2, -3)); + expected = "MULTIPOINT M((5.121320343559642 1.8786796564403572 2), (3 1 2))"; + assertEquals(expected, actual); + + geom = Constructors.geomFromEWKT("POLYGON M((0 0 1, 1 1 1, 5 1 1, 5 0 1, 1 0 1, 0 0 1))"); + Geometry finalGeom = geom; + Exception e = assertThrows(IllegalArgumentException.class, () -> Functions.locateAlong(finalGeom, 1)); + assertEquals("Polygon geometry type not supported, supported types are: (Multi)Point and (Multi)LineString.", e.getMessage()); + } + @Test public void isValidReason() { // Valid geometry diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index 660658491..cc0b77b89 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -2270,6 +2270,36 @@ Output: LINESTRING (69.28469348539744 94.28469348539744, 100 125, 111.70035626068274 140.21046313888758) ``` +## ST_LocateAlong + +Introduction: This function computes Point or MultiPoint geometries representing locations along a measured input geometry (LineString or MultiLineString) corresponding to the provided measure value(s). Polygonal geometry inputs are not supported. The output points lie directly on the input line at the specified measure positions. + +Additionally, an optional `offset` parameter can shift the resulting points left or right from the input line. A positive offset displaces the points to the left side, while a negative value offsets them to the right side by the given distance. + +This allows identifying precise locations along a measured linear geometry based on supplied measure values, with the ability to offset the output points if needed. + +Format: + +`ST_LocateAlong(linear: Geometry, measure: Double, offset: Double)` + +`ST_LocateAlong(linear: Geometry, measure: Double)` + +Since: `vTBD` + +SQL Example: + +```sql +SELECT ST_LocateAlong( + ST_GeomFromText('LINESTRING M (10 30 1, 50 50 1, 30 110 2, 70 90 2, 180 140 3, 130 190 3)') +) +``` + +Output: + +``` +MULTIPOINT M((30 110 2), (50 100 2), (70 90 2)) +``` + ## ST_LongestLine Introduction: Returns the LineString geometry representing the maximum distance between any two points from the input geometries. diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index ff0a58fbe..19ae49a8f 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -2277,6 +2277,36 @@ Output: LINESTRING (69.28469348539744 94.28469348539744, 100 125, 111.70035626068274 140.21046313888758) ``` +## ST_LocateAlong + +Introduction: This function computes Point or MultiPoint geometries representing locations along a measured input geometry (LineString or MultiLineString) corresponding to the provided measure value(s). Polygonal geometry inputs are not supported. The output points lie directly on the input line at the specified measure positions. + +Additionally, an optional `offset` parameter can shift the resulting points left or right from the input line. A positive offset displaces the points to the left side, while a negative value offsets them to the right side by the given distance. + +This allows identifying precise locations along a measured linear geometry based on supplied measure values, with the ability to offset the output points if needed. + +Format: + +`ST_LocateAlong(linear: Geometry, measure: Double, offset: Double)` + +`ST_LocateAlong(linear: Geometry, measure: Double)` + +Since: `vTBD` + +SQL Example: + +```sql +SELECT ST_LocateAlong( + ST_GeomFromText('LINESTRING M (10 30 1, 50 50 1, 30 110 2, 70 90 2, 180 140 3, 130 190 3)') +) +``` + +Output: + +``` +MULTIPOINT M((30 110 2), (50 100 2), (70 90 2)) +``` + ## ST_LongestLine Introduction: Returns the LineString geometry representing the maximum distance between any two points from the input 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 91eebd9c4..060d40df9 100644 --- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java +++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java @@ -86,6 +86,7 @@ public class Catalog { new Functions.ST_LengthSpheroid(), new Functions.ST_LineInterpolatePoint(), new Functions.ST_LineLocatePoint(), + new Functions.ST_LocateAlong(), new Functions.ST_LongestLine(), new FunctionsGeoTools.ST_Transform(), new Functions.ST_FlipCoordinates(), 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 79d1a8b26..300aeb155 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 @@ -354,6 +354,23 @@ public class Functions { } } + public static class ST_LocateAlong 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(value = "Double") Double measure, + @DataTypeHint(value = "Double") Double offset) { + Geometry linear = (Geometry) o; + return org.apache.sedona.common.Functions.locateAlong(linear, measure, offset); + } + + @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(value = "Double") Double measure) { + Geometry linear = (Geometry) o; + return org.apache.sedona.common.Functions.locateAlong(linear, measure); + } + } + public static class ST_LongestLine 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 g1, 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 f128b31d2..9ac3f83f8 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -444,6 +444,20 @@ public class FunctionTest extends TestBase{ assertEquals(expected, actual, 0.1); } + @Test + public void testLocateAlong() { + Table tbl = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('MULTILINESTRING M((1 2 3, 3 4 2, 9 4 3),(1 2 3, 5 4 5))') AS geom"); + String actual = (String) first(tbl.select(call(Functions.ST_LocateAlong.class.getSimpleName(), $("geom"), 2)).as("geom") + .select(call(Functions.ST_AsEWKT.class.getSimpleName(), $("geom")))).getField(0); + String expected = "MULTIPOINT M((3 4 2))"; + assertEquals(expected, actual); + + actual = (String) first(tbl.select(call(Functions.ST_LocateAlong.class.getSimpleName(), $("geom"), 2, -3)).as("geom") + .select(call(Functions.ST_AsEWKT.class.getSimpleName(), $("geom")))).getField(0); + expected = "MULTIPOINT M((5.121320343559642 1.8786796564403572 2), (3 1 2))"; + assertEquals(expected, actual); + } + @Test public void testLongestLine() { Table tbl = tableEnv.sqlQuery( diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py index 1a3dd9809..c206492d1 100644 --- a/python/sedona/sql/st_functions.py +++ b/python/sedona/sql/st_functions.py @@ -881,6 +881,22 @@ 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_LocateAlong(geom: ColumnOrName, measure: Union[ColumnOrName, float], offset: Optional[Union[ColumnOrName, float]] = None) -> Column: + """return locations along a measure geometry that have the given measure value. + + :param geom: + :type geom: ColumnOrName + :param measure: + :type measure: Union[ColumnOrName, float] + :param offset: + :type offset: Union[ColumnOrNameOrNumber, float] + :return: Locations along a measure geometry that have the given measure value. + :rtype: Column + """ + args = (geom, measure) if offset is None else (geom, measure, offset) + return _call_st_function("ST_LocateAlong", args) + @validate_argument_types def ST_LongestLine(geom1: ColumnOrName, geom2: ColumnOrName) -> Column: """Compute the longest line between the two geometries diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py index 2e201e538..47d3bca8d 100644 --- a/python/tests/sql/test_dataframe_api.py +++ b/python/tests/sql/test_dataframe_api.py @@ -159,6 +159,8 @@ test_configurations = [ (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_LongestLine, ("geom", "geom"), "geom_collection", "", "LINESTRING (0 0, 1 0)"), + (stf.ST_LocateAlong, ("line", 1.0), "4D_line", "ST_AsText(geom)", "MULTIPOINT ZM((1 1 1 1))"), + (stf.ST_LocateAlong, ("line", 1.0, 2.0), "4D_line", "ST_AsText(geom)", "MULTIPOINT ZM((-0.4142135623730949 2.414213562373095 1 1), (2.414213562373095 -0.4142135623730949 1 1))"), (stf.ST_HasZ, ("a",), "two_points", "", True), (stf.ST_HasM, ("point",), "4D_point", "", True), (stf.ST_M, ("point",), "4D_point", "", 4.0), @@ -360,6 +362,9 @@ wrong_type_configurations = [ (stf.ST_LongestLine, (None, "")), (stf.ST_LongestLine, (None, None)), (stf.ST_LongestLine, ("", None)), + (stf.ST_LocateAlong, (None, "")), + (stf.ST_LocateAlong, (None, None)), + (stf.ST_LocateAlong, ("", None)), (stf.ST_HasZ, (None,)), (stf.ST_HasM, (None,)), (stf.ST_M, (None,)), diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index fcedfb794..d26d1ea38 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -1403,6 +1403,16 @@ class TestPredicateJoin(TestBase): "select ST_AsText(ST_LineFromMultiPoint(ST_GeomFromText({})))".format(input_geom)) assert line_geometry.take(1)[0][0] == expected_geom + def test_st_locate_along(self): + baseDf = self.spark.sql("SELECT ST_GeomFromWKT('MULTILINESTRING M((1 2 3, 3 4 2, 9 4 3),(1 2 3, 5 4 5))') as geom") + actual = baseDf.selectExpr("ST_AsText(ST_LocateAlong(geom, 2))").take(1)[0][0] + expected = "MULTIPOINT M((3 4 2))" + assert expected == actual + + actual = baseDf.selectExpr("ST_AsText(ST_LocateAlong(geom, 2, -3))").take(1)[0][0] + expected = "MULTIPOINT M((5.121320343559642 1.8786796564403572 2), (3 1 2))" + assert expected == actual + def test_st_longest_line(self): basedf = self.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((40 180, 110 160, 180 180, 180 120, 140 90, 160 40, 80 10, 70 40, 20 50, 40 180),(60 140, 99 77.5, 90 140, 60 140))') as geom") actual = basedf.selectExpr("ST_AsText(ST_LongestLine(geom, geom))").take(1)[0][0] diff --git a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java index 6f9448783..b379a6c3e 100644 --- a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java +++ b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java @@ -590,7 +590,6 @@ public class TestFunctionsV2 "LINESTRING(45.173118104 45.743370112,50 20,90 80,112.975930502 49.365425998)" ); } - @Test public void test_ST_LongestLine() { registerUDFV2("ST_LongestLine", String.class, String.class); 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 6d16bc9be..11d0b46dd 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 @@ -156,6 +156,7 @@ object Catalog { function[ST_LineSubstring](), function[ST_LineInterpolatePoint](), function[ST_LineLocatePoint](), + function[ST_LocateAlong](), function[ST_LongestLine](), function[ST_SubDivideExplode](), function[ST_SubDivide](), 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 ded1e31f7..8dd20662a 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 @@ -1209,6 +1209,14 @@ case class ST_LengthSpheroid(inputExpressions: Seq[Expression]) } } +case class ST_LocateAlong(inputExpressions: Seq[Expression]) + extends InferredExpression(inferrableFunction3(Functions.locateAlong), inferrableFunction2(Functions.locateAlong)) { + + protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { + copy(inputExpressions = newChildren) + } +} + case class ST_LongestLine(inputExpressions: Seq[Expression]) extends InferredExpression(Functions.longestLine _) { 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 9bb23391d..9d28c2ae4 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 @@ -224,6 +224,11 @@ object st_functions extends DataFrameAPI { def ST_LongestLine(geom1: Column, geom2: Column): Column = wrapExpression[ST_LongestLine](geom1, geom2) def ST_LongestLine(geom1: String, geom2: String): Column = wrapExpression[ST_LongestLine](geom1, geom2) + def ST_LocateAlong(geom: Column, measure: Column, offset: Column): Column = wrapExpression[ST_LocateAlong](geom, measure, offset) + def ST_LocateAlong(geom: String, measure: Double, offset: Double): Column = wrapExpression[ST_LocateAlong](geom, measure, offset) + def ST_LocateAlong(geom: Column, measure: Column): Column = wrapExpression[ST_LocateAlong](geom, measure) + def ST_LocateAlong(geom: String, measure: Double): Column = wrapExpression[ST_LocateAlong](geom, measure) + def ST_HasZ(geoms: Column): Column = wrapExpression[ST_HasZ](geoms) def ST_HasZ(geoms: String): Column = wrapExpression[ST_HasZ](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 4a1bf9f98..b5011c872 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 @@ -992,6 +992,17 @@ class dataFrameAPITestScala extends TestBaseScala { assert(actualResult == expectedResult) } + it("Passed ST_LocateAlong") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTILINESTRING M((1 2 3, 3 4 2, 9 4 3),(1 2 3, 5 4 5))') AS geom") + var actual = baseDf.select(ST_AsText(ST_LocateAlong("geom", 2))).first().get(0) + var expected = "MULTIPOINT M((3 4 2))" + assertEquals(expected, actual) + + actual = baseDf.select(ST_AsText(ST_LocateAlong("geom", 2, -3))).first().get(0) + expected = "MULTIPOINT M((5.121320343559642 1.8786796564403572 2), (3 1 2))" + assertEquals(expected, actual) + } + it("Passed ST_LongestLine") { val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((40 180, 110 160, 180 180, 180 120, 140 90, 160 40, 80 10, 70 40, 20 50, 40 180),(60 140, 99 77.5, 90 140, 60 140))') as geom") val actual = baseDf.select(ST_LongestLine("geom", "geom")).first().get(0).asInstanceOf[Geometry].toText 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 1b81fb909..0c3cb07aa 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 @@ -1644,6 +1644,17 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample "GEOMETRYCOLLECTION EMPTY") } + it("Should pass ST_LocateAlong") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTILINESTRING M((1 2 3, 3 4 2, 9 4 3),(1 2 3, 5 4 5))') AS geom") + var actual = baseDf.selectExpr("ST_AsText(ST_LocateAlong(geom, 2))").first().get(0) + var expected = "MULTIPOINT M((3 4 2))" + assertEquals(expected, actual) + + actual = baseDf.selectExpr("ST_AsText(ST_LocateAlong(geom, 2, -3))").first().get(0) + expected = "MULTIPOINT M((5.121320343559642 1.8786796564403572 2), (3 1 2))" + assertEquals(expected, actual) + } + it("Should pass ST_LongestLine") { val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((40 180, 110 160, 180 180, 180 120, 140 90, 160 40, 80 10, 70 40, 20 50, 40 180),(60 140, 99 77.5, 90 140, 60 140))') as geom") val actual = baseDf.selectExpr("ST_LongestLine(geom, geom)").first().get(0).asInstanceOf[Geometry].toText
