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/sedona.git
The following commit(s) were added to refs/heads/master by this push:
new 34c4ad1495 [SEDONA-676] Add ST_Perimeter (#1686)
34c4ad1495 is described below
commit 34c4ad1495f4aca9f94f9853223a8bfeff36bc12
Author: Furqaan Khan <[email protected]>
AuthorDate: Thu Nov 21 00:39:30 2024 -0500
[SEDONA-676] Add ST_Perimeter (#1686)
* feat: add one variant of ST_Perimeter and temp commit
* feat: add ST_Perimeter with 2 and 3 arg versions
* fix: test by having low decimal tolerance
* fix: snowflake tests
* feat: lenient assumes that input geom is 4326. fix: snowflake tests
* fix: snowflake tests and remove V2 test as GeoJSON doesn't contain SRID
* fix: minor snowflake test bug
* fix: minor snowflake test bug 2/2
---
.../java/org/apache/sedona/common/Functions.java | 40 ++++++++++++
.../org/apache/sedona/common/FunctionsTest.java | 73 ++++++++++++++++++++++
docs/api/flink/Function.md | 45 +++++++++++++
docs/api/snowflake/vector-data/Function.md | 43 +++++++++++++
docs/api/sql/Function.md | 45 +++++++++++++
.../main/java/org/apache/sedona/flink/Catalog.java | 1 +
.../apache/sedona/flink/expressions/Functions.java | 27 ++++++++
.../java/org/apache/sedona/flink/FunctionTest.java | 26 ++++++++
python/sedona/sql/st_functions.py | 24 +++++++
python/tests/sql/test_dataframe_api.py | 9 +++
python/tests/sql/test_function.py | 19 ++++++
.../sedona/snowflake/snowsql/TestFunctions.java | 19 ++++++
.../sedona/snowflake/snowsql/TestFunctionsV2.java | 12 ++++
.../org/apache/sedona/snowflake/snowsql/UDFs.java | 15 +++++
.../apache/sedona/snowflake/snowsql/UDFsV2.java | 24 +++++++
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 +
.../sql/sedona_sql/expressions/Functions.scala | 11 ++++
.../sql/sedona_sql/expressions/st_functions.scala | 11 ++++
.../apache/sedona/sql/dataFrameAPITestScala.scala | 19 ++++++
.../org/apache/sedona/sql/functionTestScala.scala | 18 ++++++
20 files changed, 482 insertions(+)
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 c93bae3e2f..8502c241bd 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -1094,6 +1094,46 @@ public class Functions {
return geom.getFactory().createPoint(interPoint);
}
+ public static double perimeter(Geometry geometry, boolean use_spheroid,
boolean lenient) {
+ if (use_spheroid && geometry.getSRID() != 4326) {
+ if (!lenient) {
+ throw new IllegalArgumentException(
+ "For spheroidal perimeter calculations, the input geometry must be
in the WGS84 CRS (SRID 4326).");
+ }
+ }
+
+ String geomType = geometry.getGeometryType();
+ if (geomType.equalsIgnoreCase(Geometry.TYPENAME_POLYGON)) {
+ return calculateLength(geometry, use_spheroid);
+ } else if (geomType.equalsIgnoreCase(Geometry.TYPENAME_MULTIPOLYGON)) {
+ return calculateLength(geometry, use_spheroid);
+ } else if
(geomType.equalsIgnoreCase(Geometry.TYPENAME_GEOMETRYCOLLECTION)) {
+ double perimeter = 0;
+ for (int i = 0; i < geometry.getNumGeometries(); i++) {
+ perimeter += perimeter(geometry.getGeometryN(i), use_spheroid,
lenient);
+ }
+ return perimeter;
+ } else {
+ return 0;
+ }
+ }
+
+ private static double calculateLength(Geometry geometry, boolean
use_spheroid) {
+ if (use_spheroid) {
+ return Spheroid.length(geometry);
+ } else {
+ return length(geometry);
+ }
+ }
+
+ public static double perimeter(Geometry geometry, boolean use_spheroid) {
+ return perimeter(geometry, use_spheroid, true);
+ }
+
+ public static double perimeter(Geometry geometry) {
+ return perimeter(geometry, false);
+ }
+
/**
* Forces a Polygon/MultiPolygon to use clockwise orientation for the
exterior ring and a
* counter-clockwise for the interior ring(s).
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 74ab3483b0..72b850b900 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -1100,6 +1100,79 @@ public class FunctionsTest extends TestBase {
assertEquals(0, expected.compareTo(actual,
COORDINATE_SEQUENCE_COMPARATOR));
}
+ @Test
+ public void testPerimeter() throws ParseException {
+ Geometry geom =
+ Constructors.geomFromWKT(
+ "MULTIPOLYGON(((763104.471273676 2949418.44119003,763104.477769673
2949418.42538203,763104.189609677 2949418.22343004,763104.471273676
2949418.44119003)),((763104.471273676 2949418.44119003,763095.804579742
2949436.33850239,763086.132105649 2949451.46730207,763078.452329651
2949462.11549407,763075.354136904 2949466.17407812,763064.362142565
2949477.64291974,763059.953961626 2949481.28983009,762994.637609571
2949532.04103014,762990.568508415 2949535.06640477,762986.710889563 2 [...]
+ 2249);
+ double actual = Functions.perimeter(geom);
+ double expected = 845.227713366824;
+ assertEquals(expected, actual, FP_TOLERANCE);
+
+ geom =
+ Constructors.geomFromWKT(
+ "GEOMETRYCOLLECTION (POLYGON ((0 0, 1 0, 1 1, 0.5 1.5, 0 1, 0 0)),
MULTIPOLYGON (((2 2, 2 3, 3 3, 3 2, 2 2)), ((4 4, 4 5, 5 5, 5 4, 4 4), (4.2
4.2, 4.8 4.2, 4.8 4.8, 4.2 4.8, 4.2 4.2))))",
+ 0);
+ actual = Functions.perimeter(geom);
+ expected = 14.814213562373094;
+ assertEquals(expected, actual, FP_TOLERANCE);
+
+ geom = Constructors.geomFromWKT("LINESTRING (0 0, 100 100)", 0);
+ actual = Functions.perimeter(geom);
+ expected = 0;
+ assertEquals(expected, actual, FP_TOLERANCE);
+
+ geom =
+ Constructors.geomFromWKT(
+ "GEOMETRYCOLLECTION(LINESTRING (6 6, 7 7, 8 6), POINT (9 9), POINT
(10 10))", 0);
+ actual = Functions.perimeter(geom);
+ expected = 0;
+ assertEquals(expected, actual, FP_TOLERANCE);
+ }
+
+ @Test
+ public void testPerimeterSpherical() throws ParseException {
+ Geometry geom =
+ Constructors.geomFromWKT(
+ "MULTIPOLYGON (((-122.33 47.61, -122.32 47.62, -122.31 47.61,
-122.30 47.62, -122.29 47.61, -122.30 47.60, -122.31 47.59, -122.32 47.60,
-122.33 47.61), (-122.315 47.605, -122.305 47.615, -122.295 47.605, -122.305
47.595, -122.315 47.605)), ((-122.35 47.65, -122.34 47.66, -122.33 47.65,
-122.32 47.66, -122.31 47.65, -122.32 47.64, -122.33 47.63, -122.34 47.64,
-122.35 47.65)))",
+ 4326);
+ double actual = Functions.perimeter(geom, true, false);
+ double expected = 26841.6072;
+ assertEquals(expected, actual, FP_TOLERANCE2);
+
+ geom =
+ Constructors.geomFromWKT(
+ "POLYGON((-122.33 47.61, -122.32 47.62, -122.31 47.61, -122.30
47.62, -122.29 47.61, -122.30 47.60, -122.31 47.59, -122.32 47.60, -122.33
47.61), (-122.315 47.605, -122.305 47.615, -122.295 47.605, -122.305 47.595,
-122.315 47.605))",
+ 4326);
+ actual = Functions.perimeter(geom, true, false);
+ expected = 16106.5064;
+ assertEquals(expected, actual, FP_TOLERANCE2);
+
+ geom =
+ Constructors.geomFromWKT(
+ "POLYGON((-122.33 47.61, -122.32 47.62, -122.31 47.61, -122.30
47.62, -122.29 47.61, -122.30 47.60, -122.31 47.59, -122.32 47.60, -122.33
47.61))",
+ 4326);
+ actual = Functions.perimeter(geom, true, false);
+ expected = 10737.6184;
+ assertEquals(expected, actual, FP_TOLERANCE2);
+
+ geom.setSRID(0);
+ actual = Functions.perimeter(geom, true);
+ expected = 10737.6184;
+ assertEquals(expected, actual, FP_TOLERANCE2);
+
+ // ignores the LineString and just calculates the perimeter of the Polygon
in this Geometry
+ // Collection
+ geom =
+ Constructors.geomFromWKT(
+ "GEOMETRYCOLLECTION(LINESTRING(10 10, 20 20, 30 10),POLYGON((40
40, 50 40, 50 50, 40 50, 40 40)))",
+ 4326);
+ actual = Functions.perimeter(geom, true, false);
+ expected = 3792549.0135;
+ assertEquals(expected, actual, FP_TOLERANCE2);
+ }
+
@Test
public void testForcePolygonCW() throws ParseException {
Geometry polyCCW =
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 56c358a3bf..8bcaa9e41d 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -3044,6 +3044,51 @@ Output:
2
```
+## ST_Perimeter
+
+Introduction: This function calculates the 2D perimeter of a given geometry.
It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long
as the GeometryCollection contains polygonal geometries). For other types, it
returns 0. To measure lines, use [ST_Length](#st_length).
+
+To get the perimeter in meters, set `use_spheroid` to `true`. This calculates
the geodesic perimeter using the WGS84 spheroid. When using `use_spheroid`, the
`lenient` parameter defaults to true, assuming the geometry uses EPSG:4326. To
throw an exception instead, set `lenient` to `false`.
+
+Format:
+
+`ST_Perimeter(geom: Geometry)`
+
+`ST_Perimeter(geom: Geometry, use_spheroid: Boolean)`
+
+`ST_Perimeter(geom: Geometry, use_spheroid: Boolean, lenient: Boolean = True)`
+
+Since: `v1.7.0`
+
+SQL Example:
+
+```sql
+SELECT ST_Perimeter(
+ ST_GeomFromText('POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))')
+)
+```
+
+Output:
+
+```
+20.0
+```
+
+SQL Example:
+
+```sql
+SELECT ST_Perimeter(
+ ST_GeomFromText('POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))', 4326),
+ true, false
+)
+```
+
+Output:
+
+```
+2216860.5497177234
+```
+
## ST_PointN
Introduction: Return the Nth point in a single linestring or circular
linestring in the geometry. Negative values are counted backwards from the end
of the LineString, so that -1 is the last point. Returns NULL if there is no
linestring in the geometry.
diff --git a/docs/api/snowflake/vector-data/Function.md
b/docs/api/snowflake/vector-data/Function.md
index d3dc43d772..7d0e6e408d 100644
--- a/docs/api/snowflake/vector-data/Function.md
+++ b/docs/api/snowflake/vector-data/Function.md
@@ -2278,6 +2278,49 @@ SELECT ST_NumPoints(ST_GeomFromText('LINESTRING(0 1, 1
0, 2 0)'))
Output: `3`
+## ST_Perimeter
+
+Introduction: This function calculates the 2D perimeter of a given geometry.
It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long
as the GeometryCollection contains polygonal geometries). For other types, it
returns 0. To measure lines, use [ST_Length](#st_length).
+
+To get the perimeter in meters, set `use_spheroid` to `true`. This calculates
the geodesic perimeter using the WGS84 spheroid. When using `use_spheroid`, the
`lenient` parameter defaults to true, assuming the geometry uses EPSG:4326. To
throw an exception instead, set `lenient` to `false`.
+
+Format:
+
+`ST_Perimeter(geom: Geometry)`
+
+`ST_Perimeter(geom: Geometry, use_spheroid: Boolean)`
+
+`ST_Perimeter(geom: Geometry, use_spheroid: Boolean, lenient: Boolean = True)`
+
+SQL Example:
+
+```sql
+SELECT ST_Perimeter(
+ ST_GeomFromText('POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))')
+)
+```
+
+Output:
+
+```
+20.0
+```
+
+SQL Example:
+
+```sql
+SELECT ST_Perimeter(
+ ST_GeomFromText('POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))', 4326),
+ true, false
+)
+```
+
+Output:
+
+```
+2216860.5497177234
+```
+
## ST_PointN
Introduction: Return the Nth point in a single linestring or circular
linestring in the geometry. Negative values are counted backwards from the end
of the LineString, so that -1 is the last point. Returns NULL if there is no
linestring in the geometry.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index d909bd3cab..ac63ca7cf9 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -3129,6 +3129,51 @@ Output:
3
```
+## ST_Perimeter
+
+Introduction: This function calculates the 2D perimeter of a given geometry.
It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long
as the GeometryCollection contains polygonal geometries). For other types, it
returns 0. To measure lines, use [ST_Length](#st_length).
+
+To get the perimeter in meters, set `use_spheroid` to `true`. This calculates
the geodesic perimeter using the WGS84 spheroid. When using `use_spheroid`, the
`lenient` parameter defaults to true, assuming the geometry uses EPSG:4326. To
throw an exception instead, set `lenient` to `false`.
+
+Format:
+
+`ST_Perimeter(geom: Geometry)`
+
+`ST_Perimeter(geom: Geometry, use_spheroid: Boolean)`
+
+`ST_Perimeter(geom: Geometry, use_spheroid: Boolean, lenient: Boolean = True)`
+
+Since: `v1.7.0`
+
+SQL Example:
+
+```sql
+SELECT ST_Perimeter(
+ ST_GeomFromText('POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))')
+)
+```
+
+Output:
+
+```
+20.0
+```
+
+SQL Example:
+
+```sql
+SELECT ST_Perimeter(
+ ST_GeomFromText('POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))', 4326),
+ true, false
+)
+```
+
+Output:
+
+```
+2216860.5497177234
+```
+
## ST_PointN
Introduction: Return the Nth point in a single linestring or circular
linestring in the geometry. Negative values are counted backwards from the end
of the LineString, so that -1 is the last point. Returns NULL if there is no
linestring in 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 5844fc30f0..396ad16cf0 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -100,6 +100,7 @@ public class Catalog {
new FunctionsGeoTools.ST_Transform(),
new Functions.ST_FlipCoordinates(),
new Functions.ST_GeoHash(),
+ new Functions.ST_Perimeter(),
new Functions.ST_PointOnSurface(),
new Functions.ST_Scale(),
new Functions.ST_ScaleGeom(),
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 83d53e999b..15789ad25a 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
@@ -568,6 +568,33 @@ public class Functions {
}
}
+ public static class ST_Perimeter extends ScalarFunction {
+ @DataTypeHint(value = "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.perimeter(geom);
+ }
+
+ @DataTypeHint(value = "Double")
+ public Double eval(
+ @DataTypeHint(value = "RAW", bridgedTo =
org.locationtech.jts.geom.Geometry.class) Object o,
+ Boolean use_spheroid) {
+ Geometry geom = (Geometry) o;
+ return org.apache.sedona.common.Functions.perimeter(geom, use_spheroid);
+ }
+
+ @DataTypeHint(value = "Double")
+ public Double eval(
+ @DataTypeHint(value = "RAW", bridgedTo =
org.locationtech.jts.geom.Geometry.class) Object o,
+ Boolean use_spheroid,
+ boolean lenient) {
+ Geometry geom = (Geometry) o;
+ return org.apache.sedona.common.Functions.perimeter(geom, use_spheroid,
lenient);
+ }
+ }
+
public static class ST_PointOnSurface extends ScalarFunction {
@DataTypeHint(value = "RAW", bridgedTo =
org.locationtech.jts.geom.Geometry.class)
public Geometry eval(
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 d3d569d940..cbb9fec605 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -764,6 +764,32 @@ public class FunctionTest extends TestBase {
assertEquals("POINTM", first(pointTable).getField(0));
}
+ @Test
+ public void testPerimeter() {
+ Table polygonTable = createPolygonTable(testDataSize);
+ Table perimeterTable =
+ polygonTable.select(
+ call(Functions.ST_Perimeter.class.getSimpleName(),
$(polygonColNames[0])));
+ Double perimeter = (Double) first(perimeterTable).getField(0);
+ assertEquals(4.0, perimeter, FP_TOLERANCE);
+
+ polygonTable =
+ tableEnv.sqlQuery(
+ "SELECT ST_GeomFromWKT('POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))',
4326) AS geom");
+ perimeterTable =
+ polygonTable.select(
+ call(Functions.ST_Perimeter.class.getSimpleName(), $("geom"),
true, false));
+ perimeter = (Double) first(perimeterTable).getField(0);
+ assertEquals(443770.91724830196, perimeter, FP_TOLERANCE);
+
+ polygonTable =
+ tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POLYGON ((0 0, 0 1, 1 1, 1
0, 0 0))') AS geom");
+ perimeterTable =
+ polygonTable.select(call(Functions.ST_Perimeter.class.getSimpleName(),
$("geom"), true));
+ perimeter = (Double) first(perimeterTable).getField(0);
+ assertEquals(443770.91724830196, perimeter, FP_TOLERANCE);
+ }
+
@Test
public void testPointOnSurface() {
Table pointTable = createPointTable_real(testDataSize);
diff --git a/python/sedona/sql/st_functions.py
b/python/sedona/sql/st_functions.py
index 3bec08d7bf..9e77909c15 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -1170,6 +1170,30 @@ def ST_MakeLine(geom1: ColumnOrName, geom2:
Optional[ColumnOrName] = None) -> Co
return _call_st_function("ST_MakeLine", args)
+@validate_argument_types
+def ST_Perimeter(
+ geom: ColumnOrName,
+ use_spheroid: Optional[Union[ColumnOrName, bool]] = None,
+ lenient: Optional[Union[ColumnOrName, bool]] = None,
+) -> Column:
+ """Returns the perimeter of a Polygon/MultiPolygon geometries. Otherwise,
returns 0
+
+ @param geom: Polygonal geometry
+ @param use_spheroid: Use Spheroid
+ @param lenient: suppresses the exception
+ @return: Perimeter of a Polygon/MultiPolygon geometries
+ """
+
+ args = (geom, use_spheroid, lenient)
+
+ if lenient is None:
+ if use_spheroid is None:
+ args = (geom,)
+ else:
+ args = (geom, use_spheroid)
+ return _call_st_function("ST_Perimeter", args)
+
+
@validate_argument_types
def ST_Points(geometry: ColumnOrName) -> Column:
"""Creates a MultiPoint geometry consisting of all the coordinates of the
input geometry
diff --git a/python/tests/sql/test_dataframe_api.py
b/python/tests/sql/test_dataframe_api.py
index 9c5c65e093..5683d6d29c 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -731,6 +731,15 @@ test_configurations = [
0.2927864015850548,
),
(stf.ST_MaxDistance, ("a", "b"), "overlapping_polys", "",
3.1622776601683795),
+ (stf.ST_Perimeter, ("geom",), "triangle_geom", "", 3.414213562373095),
+ (stf.ST_Perimeter, ("geom", True), "triangle_geom", "ceil(geom)", 378794),
+ (
+ stf.ST_Perimeter,
+ (lambda: stf.ST_SetSRID("geom", 4326), True),
+ "triangle_geom",
+ "ceil(geom)",
+ 378794,
+ ),
(
stf.ST_Points,
("line",),
diff --git a/python/tests/sql/test_function.py
b/python/tests/sql/test_function.py
index ec21d853b1..e92dbe4c7f 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -1649,6 +1649,25 @@ class TestPredicateJoin(TestBase):
for actual, expected in result:
assert actual == expected
+ def test_st_perimeter(self):
+ baseDf = self.spark.sql(
+ "SELECT ST_GeomFromWKT('POLYGON((743238 2967416,743238
2967450,743265 2967450,743265.625 2967416,743238 2967416))') AS geom"
+ )
+ actual = baseDf.selectExpr("ST_Perimeter(geom)").take(1)[0][0]
+ expected = 122.63074400009504
+ assert actual == expected
+
+ baseDf = self.spark.sql(
+ "SELECT ST_GeomFromWKT('POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))',
4326) AS geom"
+ )
+ actual = baseDf.selectExpr("ST_Perimeter(geom, true)").first()[0]
+ expected = 443770.91724830196
+ assert expected == actual
+
+ actual = baseDf.selectExpr("ST_Perimeter(geom, true,
false)").first()[0]
+ expected = 443770.91724830196
+ assert expected == actual
+
def test_st_points(self):
# Given
geometry_df = self.spark.createDataFrame(
diff --git
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java
index 157994b329..c3fdd3160a 100644
---
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java
+++
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java
@@ -769,6 +769,25 @@ public class TestFunctions extends TestBase {
"POINT (5 6)");
}
+ @Test
+ public void test_ST_Perimeter() {
+ registerUDF("ST_Perimeter", byte[].class);
+ verifySqlSingleRes(
+ "SELECT sedona.ST_Perimeter(sedona.ST_GeomFromText('POLYGON((0 0, 0 5,
5 5, 5 0, 0 0))'))",
+ 20.0);
+
+ registerUDF("ST_Perimeter", byte[].class, boolean.class);
+ verifySqlSingleRes(
+ "SELECT CEIL(sedona.ST_Perimeter(sedona.ST_GeomFromText('POLYGON((0 0,
0 5, 5 5, 5 0, 0 0))'), true))",
+ 2216861.0);
+
+ registerUDF("ST_Perimeter", byte[].class, boolean.class, boolean.class);
+ registerUDF("ST_GeomFromText", String.class, int.class);
+ verifySqlSingleRes(
+ "SELECT CEIL(sedona.ST_Perimeter(sedona.ST_GeomFromText('POLYGON((0 0,
0 5, 5 5, 5 0, 0 0))', 4326), true, false))",
+ 2216861.0);
+ }
+
@Test
public void test_ST_PointOnSurface() {
registerUDF("ST_PointOnSurface", byte[].class);
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 a387f5d9e6..e1b9ccc7de 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
@@ -719,6 +719,18 @@ public class TestFunctionsV2 extends TestBase {
"POINT(5 6)");
}
+ @Test
+ public void test_ST_Perimeter() {
+ registerUDFV2("ST_Perimeter", String.class);
+ verifySqlSingleRes(
+ "SELECT sedona.ST_Perimeter(ST_GeomFromText('POLYGON((0 0, 0 5, 5 5, 5
0, 0 0))'))", 20.0);
+
+ registerUDFV2("ST_Perimeter", String.class, boolean.class);
+ verifySqlSingleRes(
+ "SELECT CEIL(sedona.ST_Perimeter(ST_GeomFromText('POLYGON((0 0, 0 5, 5
5, 5 0, 0 0))'), true))",
+ 2216861.0);
+ }
+
@Test
public void test_ST_PointOnSurface() {
registerUDFV2("ST_PointOnSurface", String.class);
diff --git
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
index 83c075cd6b..368cdb3e4b 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
@@ -807,6 +807,21 @@ public class UDFs {
return
GeometrySerde.serialize(Functions.pointN(GeometrySerde.deserialize(geometry),
n));
}
+ @UDFAnnotations.ParamMeta(argNames = {"geometry"})
+ public static double ST_Perimeter(byte[] geometry) {
+ return Functions.perimeter(GeometrySerde.deserialize(geometry));
+ }
+
+ @UDFAnnotations.ParamMeta(argNames = {"geometry", "use_spheroid"})
+ public static double ST_Perimeter(byte[] geometry, boolean use_spheroid) {
+ return Functions.perimeter(GeometrySerde.deserialize(geometry),
use_spheroid);
+ }
+
+ @UDFAnnotations.ParamMeta(argNames = {"geometry", "use_spheroid", "lenient"})
+ public static double ST_Perimeter(byte[] geometry, boolean use_spheroid,
boolean lenient) {
+ return Functions.perimeter(GeometrySerde.deserialize(geometry),
use_spheroid, lenient);
+ }
+
@UDFAnnotations.ParamMeta(argNames = {"geometry"})
public static byte[] ST_PointOnSurface(byte[] geometry) {
return
GeometrySerde.serialize(Functions.pointOnSurface(GeometrySerde.deserialize(geometry)));
diff --git
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
index a645f87836..a8e6e2efd6 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
@@ -939,6 +939,30 @@ public class UDFsV2 {
return
GeometrySerde.serGeoJson(Functions.pointN(GeometrySerde.deserGeoJson(geometry),
n));
}
+ @UDFAnnotations.ParamMeta(
+ argNames = {"geometry"},
+ argTypes = {"Geometry"},
+ returnTypes = "double")
+ public static double ST_Perimeter(String geometry) {
+ return Functions.perimeter(GeometrySerde.deserGeoJson(geometry));
+ }
+
+ @UDFAnnotations.ParamMeta(
+ argNames = {"geometry", "use_spheroid"},
+ argTypes = {"Geometry", "boolean"},
+ returnTypes = "double")
+ public static double ST_Perimeter(String geometry, boolean use_spheroid) {
+ return Functions.perimeter(GeometrySerde.deserGeoJson(geometry),
use_spheroid);
+ }
+
+ @UDFAnnotations.ParamMeta(
+ argNames = {"geometry", "use_spheroid", "lenient"},
+ argTypes = {"Geometry", "boolean", "boolean"},
+ returnTypes = "double")
+ public static double ST_Perimeter(String geometry, boolean use_spheroid,
boolean lenient) {
+ return Functions.perimeter(GeometrySerde.deserGeoJson(geometry),
use_spheroid, lenient);
+ }
+
@UDFAnnotations.ParamMeta(
argNames = {"geometry"},
argTypes = {"Geometry"},
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 442d0203c7..b491375379 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
@@ -55,6 +55,7 @@ object Catalog {
function[ST_GeomFromGML](),
function[ST_GeomFromKML](),
function[ST_CoordDim](),
+ function[ST_Perimeter](),
function[ST_Point](),
function[ST_Points](),
function[ST_MakeEnvelope](),
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 91f833e936..dc8a290b8a 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
@@ -993,6 +993,17 @@ case class ST_MakeLine(inputExpressions: Seq[Expression])
}
}
+case class ST_Perimeter(inputExpressions: Seq[Expression])
+ extends InferredExpression(
+ inferrableFunction3(Functions.perimeter),
+ inferrableFunction2(Functions.perimeter),
+ inferrableFunction1(Functions.perimeter)) {
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) =
{
+ copy(inputExpressions = newChildren)
+ }
+}
+
case class ST_Points(inputExpressions: Seq[Expression])
extends InferredExpression(Functions.points _) {
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 e7b11c3c17..7bb753cc28 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
@@ -358,6 +358,17 @@ object st_functions extends DataFrameAPI {
def ST_MakeLine(geom1: String, geom2: String): Column =
wrapExpression[ST_MakeLine](geom1, geom2)
+ def ST_Perimeter(geom: Column): Column = wrapExpression[ST_Perimeter](geom)
+ def ST_Perimeter(geom: String): Column = wrapExpression[ST_Perimeter](geom)
+ def ST_Perimeter(geom: Column, use_spheroid: Column): Column =
+ wrapExpression[ST_Perimeter](geom, use_spheroid)
+ def ST_Perimeter(geom: String, use_spheroid: Boolean): Column =
+ wrapExpression[ST_Perimeter](geom, use_spheroid)
+ def ST_Perimeter(geom: Column, use_spheroid: Column, lenient: Column):
Column =
+ wrapExpression[ST_Perimeter](geom, use_spheroid, lenient)
+ def ST_Perimeter(geom: String, use_spheroid: Boolean, lenient: Boolean):
Column =
+ wrapExpression[ST_Perimeter](geom, use_spheroid, lenient)
+
def ST_Points(geom: Column): Column = wrapExpression[ST_Points](geom)
def ST_Points(geom: String): Column = wrapExpression[ST_Points](geom)
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 e0dbe5a60a..cf9b8a0f7a 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
@@ -760,6 +760,25 @@ class dataFrameAPITestScala extends TestBaseScala {
assert(actualResult.toText() == expectedResult)
}
+ it("Passed ST_Perimeter") {
+ var baseDf = sparkSession.sql(
+ "SELECT ST_GeomFromWKT('POLYGON((743238 2967416,743238 2967450,743265
2967450,743265.625 2967416,743238 2967416))') AS geom")
+ var actual = baseDf.select(ST_Perimeter("geom")).first().get(0)
+ var expected = 122.63074400009504
+ assertEquals(expected, actual)
+
+ baseDf = sparkSession.sql(
+ "SELECT ST_GeomFromWKT('POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))', 4326) AS
geom")
+ actual = baseDf.select(ST_Perimeter("geom", use_spheroid =
true)).first().get(0)
+ expected = 443770.91724830196
+ assertEquals(expected, actual)
+
+ actual =
+ baseDf.select(ST_Perimeter("geom", use_spheroid = true, lenient =
false)).first().get(0)
+ expected = 443770.91724830196
+ assertEquals(expected, actual)
+ }
+
it("Passed ST_Project") {
val baseDf = sparkSession.sql(
"SELECT ST_GeomFromWKT('POINT(0 0)') as point, ST_MakeEnvelope(0, 1,
2, 0) as poly")
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 e727cfdd10..9bda2da441 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
@@ -595,6 +595,24 @@ class functionTestScala
assert(row.get(1).asInstanceOf[Geometry].toText.equals("LINESTRING (5 6,
7 8, 9 10)"))
}
+ it("Passed ST_Perimeter") {
+ var baseDf = sparkSession.sql(
+ "SELECT ST_GeomFromWKT('POLYGON((743238 2967416,743238 2967450,743265
2967450,743265.625 2967416,743238 2967416))') AS geom")
+ var actual = baseDf.selectExpr("ST_Perimeter(geom)").first().get(0)
+ var expected = 122.63074400009504
+ assertEquals(expected, actual)
+
+ baseDf = sparkSession.sql(
+ "SELECT ST_GeomFromWKT('POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))', 4326) AS
geom")
+ actual = baseDf.selectExpr("ST_Perimeter(geom, true)").first().get(0)
+ expected = 443770.91724830196
+ assertEquals(expected, actual)
+
+ actual = baseDf.selectExpr("ST_Perimeter(geom, true,
false)").first().get(0)
+ expected = 443770.91724830196
+ assertEquals(expected, actual)
+ }
+
it("Passed ST_Points") {
val testtable = sparkSession.sql(