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 2023/05/20 08:59:34 UTC

[sedona] branch geodestic-distance created (now 1188d524)

This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a change to branch geodestic-distance
in repository https://gitbox.apache.org/repos/asf/sedona.git


      at 1188d524 Finish Spark and Flink doc

This branch includes the following new commits:

     new 4ac1f857 Finish Spark Java/Scala
     new 96974494 Finish Sedona Spark Python
     new 7e9ce3bd Finish Sedona Flink
     new 1188d524 Finish Spark and Flink doc

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sedona] 03/04: Finish Sedona Flink

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch geodestic-distance
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 7e9ce3bd258be5c0399abb2f4d803b6abb938958
Author: Jia Yu <ji...@apache.org>
AuthorDate: Sat May 20 01:31:41 2023 -0700

    Finish Sedona Flink
---
 .../main/java/org/apache/sedona/flink/Catalog.java |  4 ++
 .../apache/sedona/flink/expressions/Functions.java | 44 +++++++++++++++++++++
 .../java/org/apache/sedona/flink/FunctionTest.java | 45 ++++++++++++++++++++++
 3 files changed, 93 insertions(+)

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 1075710a..66e4bffa 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -37,14 +37,18 @@ public class Catalog {
                 new Constructors.ST_MPolyFromText(),
                 new Constructors.ST_MLineFromText(),
                 new Functions.ST_Area(),
+                new Functions.ST_AreaSpheroid(),
                 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_DistanceSphere(),
+                new Functions.ST_DistanceSpheroid(),
                 new Functions.ST_3DDistance(),
                 new Functions.ST_Length(),
+                new Functions.ST_LengthSpheroid(),
                 new Functions.ST_Transform(),
                 new Functions.ST_FlipCoordinates(),
                 new Functions.ST_GeoHash(),
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 11f5e9d8..608a461b 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
@@ -28,6 +28,14 @@ public class Functions {
         }
     }
 
+    public static class ST_AreaSpheroid extends ScalarFunction {
+        @DataTypeHint("Double")
+        public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
+            Geometry geom = (Geometry) o;
+            return org.apache.sedona.common.sphere.Spheroid.area(geom);
+        }
+    }
+
     public static class ST_Azimuth extends ScalarFunction {
         @DataTypeHint("Double")
         public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o1,
@@ -89,6 +97,34 @@ public class Functions {
         }
     }
 
+    public static class ST_DistanceSphere extends ScalarFunction {
+        @DataTypeHint("Double")
+        public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o1,
+                @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o2) {
+            Geometry geom1 = (Geometry) o1;
+            Geometry geom2 = (Geometry) o2;
+            return org.apache.sedona.common.sphere.Haversine.distance(geom1, geom2);
+        }
+
+        @DataTypeHint("Double")
+        public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o1,
+                @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o2, @DataTypeHint("Double") Double radius) {
+            Geometry geom1 = (Geometry) o1;
+            Geometry geom2 = (Geometry) o2;
+            return org.apache.sedona.common.sphere.Haversine.distance(geom1, geom2, radius);
+        }
+    }
+
+    public static class ST_DistanceSpheroid extends ScalarFunction {
+        @DataTypeHint("Double")
+        public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o1,
+                @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o2) {
+            Geometry geom1 = (Geometry) o1;
+            Geometry geom2 = (Geometry) o2;
+            return org.apache.sedona.common.sphere.Spheroid.distance(geom1, geom2);
+        }
+    }
+
     public static class ST_3DDistance extends ScalarFunction {
         @DataTypeHint("Double")
         public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o1,
@@ -107,6 +143,14 @@ public class Functions {
         }
     }
 
+    public static class ST_LengthSpheroid extends ScalarFunction {
+        @DataTypeHint("Double")
+        public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
+            Geometry geom = (Geometry) o;
+            return org.apache.sedona.common.sphere.Spheroid.length(geom);
+        }
+    }
+
     public static class ST_YMin extends ScalarFunction {
         @DataTypeHint("Double")
         public Double 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 02486e39..b34503e3 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -53,6 +53,15 @@ public class FunctionTest extends TestBase{
         assertEquals(1.0, result, 0);
     }
 
+    @Test
+    public void testAreaSpheroid() {
+        Table tbl = tableEnv.sqlQuery(
+                "SELECT ST_AreaSpheroid(ST_GeomFromWKT('Polygon ((35 34, 30 28, 34 25, 35 34))'))");
+        Double expected = 201824850811.76245;
+        Double actual = (Double) first(tbl).getField(0);
+        assertEquals(expected, actual);
+    }
+
     @Test
     public void testAzimuth() {
         Table pointTable = tableEnv.sqlQuery("SELECT ST_Azimuth(ST_GeomFromWKT('POINT (0 0)'), ST_GeomFromWKT('POINT (1 1)'))");
@@ -153,6 +162,33 @@ public class FunctionTest extends TestBase{
         assertEquals(0.0, first(pointTable).getField(0));
     }
 
+    @Test
+    public void testDistanceSpheroid() {
+        Table tbl = tableEnv.sqlQuery(
+                "SELECT ST_DistanceSpheroid(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromWKT('POINT (55.9533 -3.1883)'))");
+        Double expected = 544430.9411996207;
+        Double actual = (Double) first(tbl).getField(0);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testDistanceSphere() {
+        Table tbl = tableEnv.sqlQuery(
+                "SELECT ST_DistanceSphere(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromWKT('POINT (55.9533 -3.1883)'))");
+        Double expected = 544405.4459192449;
+        Double actual = (Double) first(tbl).getField(0);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testDistanceSphereWithRadius() {
+        Table tbl = tableEnv.sqlQuery(
+                "SELECT ST_DistanceSphere(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromWKT('POINT (55.9533 -3.1883)'), 6378137.0)");
+        Double expected = 544405.4459192449;
+        Double actual = (Double) first(tbl).getField(0);
+        assertEquals(expected, actual);
+    }
+
     @Test
     public void test3dDistance() {
         Table pointTable = tableEnv.sqlQuery("SELECT ST_3DDistance(ST_GeomFromWKT('POINT (0 0 0)'), ST_GeomFromWKT('POINT (1 1 1)'))");
@@ -168,6 +204,15 @@ public class FunctionTest extends TestBase{
         assertEquals(4, result, 0);
     }
 
+    @Test
+    public void testLengthSpheroid() {
+        Table tbl = tableEnv.sqlQuery(
+                "SELECT ST_LengthSpheroid(ST_GeomFromWKT('Polygon ((0 0, 0 90, 0 0))'))");
+        Double expected = 20037508.342789244;
+        Double actual = (Double) first(tbl).getField(0);
+        assertEquals(expected, actual);
+    }
+
     @Test
     public void testYMax() {
         Table polygonTable = createPolygonTable(1);


[sedona] 04/04: Finish Spark and Flink doc

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch geodestic-distance
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 1188d524edd4a8674064a3cf7308f9c8deb20a3a
Author: Jia Yu <ji...@apache.org>
AuthorDate: Sat May 20 01:59:10 2023 -0700

    Finish Spark and Flink doc
---
 docs/api/flink/Function.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++
 docs/api/sql/Function.md   | 77 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 155 insertions(+)

diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 603609d8..7733fadb 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -51,6 +51,24 @@ SELECT ST_Area(polygondf.countyshape)
 FROM polygondf
 ```
 
+## ST_AreaSpheroid
+
+Introduction: Return the geodesic area of A using WGS84 spheroid. Unit is meter. Works better for large geometries (country level) compared to `ST_Area` + `ST_Transform`. It is equivalent to PostGIS `ST_Area(geography, use_spheroid=true)` function and produces nearly identical results.
+
+Geometry must be in EPSG:4326 (WGS84) projection and must be in ==lat/lon== order. You can use ==ST_FlipCoordinates== to swap lat and lon.
+
+Format: `ST_AreaSpheroid (A:geometry)`
+
+Since: `v1.4.1`
+
+Example:
+
+```sql
+SELECT ST_AreaSpheroid(ST_GeomFromWKT('Polygon ((35 34, 30 28, 34 25, 35 34))'))
+```
+
+Output: `201824850811.76245`
+
 ## ST_AsBinary
 
 Introduction: Return the Well-Known Binary representation of a geometry
@@ -258,6 +276,49 @@ SELECT ST_Distance(polygondf.countyshape, polygondf.countyshape)
 FROM polygondf
 ```
 
+## ST_DistanceSphere
+
+Introduction: Return the haversine / great-circle distance of A using a given earth radius (default radius: 6378137.0). Unit is meter. Works better for large geometries (country level) compared to `ST_Distance` + `ST_Transform`. It is equivalent to PostGIS `ST_Distance(geography, use_spheroid=false)` and `ST_DistanceSphere` function and produces nearly identical results. It provides faster but less accurate result compared to `ST_DistanceSpheroid`.
+
+Geometry must be in EPSG:4326 (WGS84) projection and must be in lat/lon order. You can use ==ST_FlipCoordinates== to swap lat and lon.
+
+Format: `ST_DistanceSphere (A:geometry)`
+
+Since: `v1.4.1`
+
+Example 1:
+
+```sql
+SELECT ST_DistanceSphere(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromWKT('POINT (55.9533 -3.1883)'))
+```
+
+Output: `544405.4459192449`
+
+Example 2:
+
+```sql
+SELECT ST_DistanceSphere(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromWKT('POINT (55.9533 -3.1883)'), 6378137.0)
+```
+
+Output: `544405.4459192449`
+
+## ST_DistanceSpheroid
+
+Introduction: Return the geodesic distance of A using WGS84 spheroid. Unit is meter. Works better for large geometries (country level) compared to `ST_Distance` + `ST_Transform`. It is equivalent to PostGIS `ST_Distance(geography, use_spheroid=true)` and `ST_DistanceSpheroid` function and produces nearly identical results. It provides slower but more accurate result compared to `ST_DistanceSphere`.
+
+Geometry must be in EPSG:4326 (WGS84) projection and must be in ==lat/lon== order. You can use ==ST_FlipCoordinates== to swap lat and lon.
+
+Format: `ST_DistanceSpheroid (A:geometry)`
+
+Since: `v1.4.1`
+
+Example:
+```sql
+SELECT ST_DistanceSpheroid(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromWKT('POINT (55.9533 -3.1883)'))
+```
+
+Output: `544430.9411996207`
+
 ## ST_Envelope
 
 Introduction: Return the envelop boundary of A
@@ -505,6 +566,23 @@ SELECT ST_Length(polygondf.countyshape)
 FROM polygondf
 ```
 
+## ST_LengthSpheroid
+
+Introduction: Return the geodesic perimeter of A using WGS84 spheroid. Unit is meter. Works better for large geometries (country level) compared to `ST_Length` + `ST_Transform`. It is equivalent to PostGIS `ST_Length(geography, use_spheroid=true)` and `ST_LengthSpheroid` function and produces nearly identical results.
+
+Geometry must be in EPSG:4326 (WGS84) projection and must be in ==lat/lon== order. You can use ==ST_FlipCoordinates== to swap lat and lon.
+
+Format: `ST_LengthSpheroid (A:geometry)`
+
+Since: `v1.4.1`
+
+Example:
+```sql
+SELECT ST_LengthSpheroid(ST_GeomFromWKT('Polygon ((0 0, 0 90, 0 0))'))
+```
+
+Output: `20037508.342789244`
+
 ## ST_LineFromMultiPoint
 
 Introduction: Creates a LineString from a MultiPoint geometry.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index f9a8d82b..cbb39841 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -49,6 +49,24 @@ SELECT ST_Area(polygondf.countyshape)
 FROM polygondf
 ```
 
+## ST_AreaSpheroid
+
+Introduction: Return the geodesic area of A using WGS84 spheroid. Unit is meter. Works better for large geometries (country level) compared to `ST_Area` + `ST_Transform`. It is equivalent to PostGIS `ST_Area(geography, use_spheroid=true)` function and produces nearly identical results.
+
+Geometry must be in EPSG:4326 (WGS84) projection and must be in ==lat/lon== order. You can use ==ST_FlipCoordinates== to swap lat and lon.
+
+Format: `ST_AreaSpheroid (A:geometry)`
+
+Since: `v1.4.1`
+
+Spark SQL example:
+
+```sql
+SELECT ST_AreaSpheroid(ST_GeomFromWKT('Polygon ((35 34, 30 28, 34 25, 35 34))'))
+```
+
+Output: `201824850811.76245`
+
 ## ST_AsBinary
 
 Introduction: Return the Well-Known Binary representation of a geometry
@@ -396,6 +414,48 @@ SELECT ST_Distance(polygondf.countyshape, polygondf.countyshape)
 FROM polygondf
 ```
 
+## ST_DistanceSphere
+
+Introduction: Return the haversine / great-circle distance of A using a given earth radius (default radius: 6378137.0). Unit is meter. Works better for large geometries (country level) compared to `ST_Distance` + `ST_Transform`. It is equivalent to PostGIS `ST_Distance(geography, use_spheroid=false)` and `ST_DistanceSphere` function and produces nearly identical results. It provides faster but less accurate result compared to `ST_DistanceSpheroid`.
+
+Geometry must be in EPSG:4326 (WGS84) projection and must be in lat/lon order. You can use ==ST_FlipCoordinates== to swap lat and lon.
+
+Format: `ST_DistanceSphere (A:geometry)`
+
+Since: `v1.4.1`
+
+Spark SQL example 1:
+```sql
+SELECT ST_DistanceSphere(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromWKT('POINT (55.9533 -3.1883)'))
+```
+
+Output: `544405.4459192449`
+
+Spark SQL example 2:
+```sql
+SELECT ST_DistanceSphere(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromWKT('POINT (55.9533 -3.1883)'), 6378137.0)
+```
+
+Output: `544405.4459192449`
+
+
+## ST_DistanceSpheroid
+
+Introduction: Return the geodesic distance of A using WGS84 spheroid. Unit is meter. Works better for large geometries (country level) compared to `ST_Distance` + `ST_Transform`. It is equivalent to PostGIS `ST_Distance(geography, use_spheroid=true)` and `ST_DistanceSpheroid` function and produces nearly identical results. It provides slower but more accurate result compared to `ST_DistanceSphere`.
+
+Geometry must be in EPSG:4326 (WGS84) projection and must be in ==lat/lon== order. You can use ==ST_FlipCoordinates== to swap lat and lon.
+
+Format: `ST_DistanceSpheroid (A:geometry)`
+
+Since: `v1.4.1`
+
+Spark SQL example:
+```sql
+SELECT ST_DistanceSpheroid(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromWKT('POINT (55.9533 -3.1883)'))
+```
+
+Output: `544430.9411996207`
+
 ## ST_Dump
 
 Introduction: It expands the geometries. If the geometry is simple (Point, Polygon Linestring etc.) it returns the geometry
@@ -720,6 +780,23 @@ SELECT ST_Length(polygondf.countyshape)
 FROM polygondf
 ```
 
+## ST_LengthSpheroid
+
+Introduction: Return the geodesic perimeter of A using WGS84 spheroid. Unit is meter. Works better for large geometries (country level) compared to `ST_Length` + `ST_Transform`. It is equivalent to PostGIS `ST_Length(geography, use_spheroid=true)` and `ST_LengthSpheroid` function and produces nearly identical results.
+
+Geometry must be in EPSG:4326 (WGS84) projection and must be in ==lat/lon== order. You can use ==ST_FlipCoordinates== to swap lat and lon.
+
+Format: `ST_LengthSpheroid (A:geometry)`
+
+Since: `v1.4.1`
+
+Spark SQL example:
+```sql
+SELECT ST_LengthSpheroid(ST_GeomFromWKT('Polygon ((0 0, 0 90, 0 0))'))
+```
+
+Output: `20037508.342789244`
+
 ## ST_LineFromMultiPoint
 
 Introduction: Creates a LineString from a MultiPoint geometry.


[sedona] 02/04: Finish Sedona Spark Python

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch geodestic-distance
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 969744941e3648ab305a8370058fa59780d23cff
Author: Jia Yu <ji...@apache.org>
AuthorDate: Sat May 20 01:15:24 2023 -0700

    Finish Sedona Spark Python
---
 python/sedona/sql/st_functions.py      | 52 ++++++++++++++++++++++++++++++++++
 python/tests/sql/test_dataframe_api.py |  5 ++++
 2 files changed, 57 insertions(+)

diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py
index 4bb7df5b..2c5196a3 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -27,6 +27,7 @@ __all__ = [
     "ST_3DDistance",
     "ST_AddPoint",
     "ST_Area",
+    "ST_AreaSpheroid",
     "ST_AsBinary",
     "ST_AsEWKB",
     "ST_AsEWKT",
@@ -45,6 +46,8 @@ __all__ = [
     "ST_ConvexHull",
     "ST_Difference",
     "ST_Distance",
+    "ST_DistanceSphere",
+    "ST_DistanceSpheroid",
     "ST_Dump",
     "ST_DumpPoints",
     "ST_EndPoint",
@@ -64,6 +67,7 @@ __all__ = [
     "ST_IsSimple",
     "ST_IsValid",
     "ST_Length",
+    "ST_LengthSpheroid",
     "ST_LineFromMultiPoint",
     "ST_LineInterpolatePoint",
     "ST_LineMerge",
@@ -153,6 +157,17 @@ def ST_Area(geometry: ColumnOrName) -> Column:
     """
     return _call_st_function("ST_Area", geometry)
 
+@validate_argument_types
+def ST_AreaSpheroid(geometry: ColumnOrName) -> Column:
+    """Calculate the area of a geometry using WGS84 spheroid.
+
+    :param geometry: Geometry column to calculate the area of.
+    :type geometry: ColumnOrName
+    :return: Area of geometry as a double column. Unit is meter.
+    :rtype: Column
+    """
+    return _call_st_function("ST_AreaSpheroid", geometry)
+
 
 @validate_argument_types
 def ST_AsBinary(geometry: ColumnOrName) -> Column:
@@ -395,6 +410,33 @@ def ST_Distance(a: ColumnOrName, b: ColumnOrName) -> Column:
     """
     return _call_st_function("ST_Distance", (a, b))
 
+@validate_argument_types
+def ST_DistanceSpheroid(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Calculate the geodesic distance between two geometry columns using WGS84 spheroid.
+
+    :param a: Geometry column to use in the calculation.
+    :type a: ColumnOrName
+    :param b: Other geometry column to use in the calculation.
+    :type b: ColumnOrName
+    :return: Two-dimensional geodesic distance between a and b as a double column. Unit is meter.
+    :rtype: Column
+    """
+    return _call_st_function("ST_DistanceSpheroid", (a, b))
+
+@validate_argument_types
+def ST_DistanceSphere(a: ColumnOrName, b: ColumnOrName, radius: Optional[Union[ColumnOrName, float]] = 6378137.0) -> Column:
+    """Calculate the haversine/great-circle distance between two geometry columns using a given radius.
+
+    :param a: Geometry column to use in the calculation.
+    :type a: ColumnOrName
+    :param b: Other geometry column to use in the calculation.
+    :type b: ColumnOrName
+    :param radius: Radius of the sphere, defaults to 6378137.0
+    :type radius: Optional[Union[ColumnOrName, float]], optional
+    :return: Two-dimensional haversine/great-circle distance between a and b as a double column. Unit is meter.
+    :rtype: Column
+    """
+    return _call_st_function("ST_DistanceSphere", (a, b, radius))
 
 @validate_argument_types
 def ST_Dump(geometry: ColumnOrName) -> Column:
@@ -654,6 +696,16 @@ def ST_Length(geometry: ColumnOrName) -> Column:
     """
     return _call_st_function("ST_Length", geometry)
 
+@validate_argument_types
+def ST_LengthSpheroid(geometry: ColumnOrName) -> Column:
+    """Calculate the perimeter of a geometry using WGS84 spheroid.
+
+    :param geometry: Geometry column to calculate length for.
+    :type geometry: ColumnOrName
+    :return: perimeter of geometry as a double column. Unit is meter.
+    :rtype: Column
+    """
+    return _call_st_function("ST_LengthSpheroid", geometry)
 
 @validate_argument_types
 def ST_LineFromMultiPoint(geometry: ColumnOrName) -> Column:
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
index 1ad052bd..e0e6e720 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -53,6 +53,7 @@ test_configurations = [
     (stf.ST_AddPoint, ("line", lambda: f.expr("ST_Point(1.0, 1.0)")), "linestring_geom", "", "LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0, 1 1)"),
     (stf.ST_AddPoint, ("line", lambda: f.expr("ST_Point(1.0, 1.0)"), 1), "linestring_geom", "", "LINESTRING (0 0, 1 1, 1 0, 2 0, 3 0, 4 0, 5 0)"),
     (stf.ST_Area, ("geom",), "triangle_geom", "", 0.5),
+    (stf.ST_AreaSpheroid, ("point",), "point_geom", "", 0.0),
     (stf.ST_AsBinary, ("point",), "point_geom", "", "01010000000000000000000000000000000000f03f"),
     (stf.ST_AsEWKB, (lambda: f.expr("ST_SetSRID(point, 3021)"),), "point_geom", "", "0101000020cd0b00000000000000000000000000000000f03f"),
     (stf.ST_AsEWKT, (lambda: f.expr("ST_SetSRID(point, 4326)"),), "point_geom", "", "SRID=4326;POINT (0 1)"),
@@ -74,6 +75,9 @@ test_configurations = [
     (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),
+    (stf.ST_DistanceSpheroid, ("point", "point"), "point_geom", "", 0.0),
+    (stf.ST_DistanceSphere, ("point", "point"), "point_geom", "", 0.0),
+    (stf.ST_DistanceSphere, ("point", "point", "6378137.0"), "point_geom", "", 0.0),
     (stf.ST_Dump, ("geom",), "multipoint", "", ["POINT (0 0)", "POINT (1 1)"]),
     (stf.ST_DumpPoints, ("line",), "linestring_geom", "", ["POINT (0 0)", "POINT (1 0)", "POINT (2 0)", "POINT (3 0)", "POINT (4 0)", "POINT (5 0)"]),
     (stf.ST_EndPoint, ("line",), "linestring_geom", "", "POINT (5 0)"),
@@ -92,6 +96,7 @@ test_configurations = [
     (stf.ST_IsSimple, ("geom",), "triangle_geom", "", True),
     (stf.ST_IsValid, (lambda: f.col("geom"),), "triangle_geom", "", True),
     (stf.ST_Length, ("line",), "linestring_geom", "", 5.0),
+    (stf.ST_LengthSpheroid, ("point",), "point_geom", "", 0.0),
     (stf.ST_LineFromMultiPoint, ("multipoint",), "multipoint_geom", "", "LINESTRING (10 40, 40 30, 20 20, 30 10)"),
     (stf.ST_LineInterpolatePoint, ("line", 0.5), "linestring_geom", "", "POINT (2.5 0)"),
     (stf.ST_LineMerge, ("geom",), "multiline_geom", "", "LINESTRING (0 0, 1 0, 1 1, 0 0)"),


[sedona] 01/04: Finish Spark Java/Scala

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch geodestic-distance
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 4ac1f857971c120b6dd9bc6d16157b9f6c5d1303
Author: Jia Yu <ji...@apache.org>
AuthorDate: Sat May 20 00:56:59 2023 -0700

    Finish Spark Java/Scala
---
 common/pom.xml                                     |   4 +
 .../org/apache/sedona/common/sphere/Haversine.java |  65 +++++++++++
 .../org/apache/sedona/common/sphere/Spheroid.java  | 115 +++++++++++++++++++
 .../org/apache/sedona/common/FunctionsTest.java    | 124 +++++++++++++++++++++
 pom.xml                                            |  10 ++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |   4 +
 .../sql/sedona_sql/expressions/Functions.scala     |  33 ++++++
 .../sql/sedona_sql/expressions/st_functions.scala  |  16 +++
 .../apache/sedona/sql/dataFrameAPITestScala.scala  |  36 ++++++
 .../org/apache/sedona/sql/functionTestScala.scala  |  72 +++++++++++-
 10 files changed, 478 insertions(+), 1 deletion(-)

diff --git a/common/pom.xml b/common/pom.xml
index 7a291375..85edcdd0 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -77,6 +77,10 @@
             <groupId>com.esotericsoftware</groupId>
             <artifactId>kryo</artifactId>
         </dependency>
+        <dependency>
+            <groupId>net.sf.geographiclib</groupId>
+            <artifactId>GeographicLib-Java</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <sourceDirectory>src/main/java</sourceDirectory>
diff --git a/common/src/main/java/org/apache/sedona/common/sphere/Haversine.java b/common/src/main/java/org/apache/sedona/common/sphere/Haversine.java
new file mode 100644
index 00000000..ab46aae7
--- /dev/null
+++ b/common/src/main/java/org/apache/sedona/common/sphere/Haversine.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sedona.common.sphere;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+
+import static java.lang.Math.atan2;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+import static java.lang.Math.sqrt;
+import static java.lang.Math.toRadians;
+
+public class Haversine
+{
+    /**
+     * Calculate the distance between two points on the earth using the "haversine" formula.
+     * This is also known as the great-circle distance
+     * This will produce almost identical result to PostGIS ST_DistanceSphere and
+     * ST_Distance(useSpheroid=false)
+     * @param geom1 The first geometry. Each coordinate is in lat/lon order
+     * @param geom2 The second geometry. Each coordinate is in lat/lon order
+     * @return
+     */
+    public static double distance(Geometry geom1, Geometry geom2, double AVG_EARTH_RADIUS)
+    {
+        Coordinate coordinate1 = geom1.getGeometryType().equals("Point")? geom1.getCoordinate():geom1.getCentroid().getCoordinate();
+        Coordinate coordinate2 = geom2.getGeometryType().equals("Point")? geom2.getCoordinate():geom2.getCentroid().getCoordinate();
+        // Calculate the distance between the two points
+        double lat1 = coordinate1.getX();
+        double lon1 = coordinate1.getY();
+        double lat2 = coordinate2.getX();
+        double lon2 = coordinate2.getY();
+        double latDistance = toRadians(lat2 - lat1);
+        double lngDistance = toRadians(lon2 - lon1);
+        double a = sin(latDistance / 2) * sin(latDistance / 2)
+                + cos(toRadians(lat1)) * cos(toRadians(lat2))
+                * sin(lngDistance / 2) * sin(lngDistance / 2);
+        double c = 2 * atan2(sqrt(a), sqrt(1 - a));
+        return AVG_EARTH_RADIUS * c * 1.0;
+    }
+
+    // Calculate the distance between two points on the earth using the "haversine" formula.
+    // The radius of the earth is 6371.0 km
+    public static double distance(Geometry geom1, Geometry geom2)
+    {
+        return distance(geom1, geom2, 6378137.0);
+    }
+}
diff --git a/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java b/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java
new file mode 100644
index 00000000..b8a016ca
--- /dev/null
+++ b/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sedona.common.sphere;
+
+import net.sf.geographiclib.Geodesic;
+import net.sf.geographiclib.GeodesicData;
+import net.sf.geographiclib.PolygonArea;
+import net.sf.geographiclib.PolygonResult;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+
+import static java.lang.Math.abs;
+
+public class Spheroid
+{
+    /**
+     * Calculate the distance between two points on the earth using the Spheroid formula.
+     * This algorithm does not use the radius of the earth, but instead uses the WGS84 ellipsoid.
+     * This is similar to the Vincenty algorithm,but use the algorithm in
+     * C. F. F. Karney, Algorithms for geodesics, J. Geodesy 87(1), 43–55 (2013)
+     * It uses the implementation from GeographicLib so please expect a small difference
+     * This will produce almost identical result to PostGIS ST_DistanceSpheroid and
+     * PostGIS ST_Distance(useSpheroid=true)
+     * @param geom1
+     * @param geom2
+     * @return
+     */
+    public static double distance(Geometry geom1, Geometry geom2) {
+        Coordinate coordinate1 = geom1.getGeometryType().equals("Point")? geom1.getCoordinate():geom1.getCentroid().getCoordinate();
+        Coordinate coordinate2 = geom2.getGeometryType().equals("Point")? geom2.getCoordinate():geom2.getCentroid().getCoordinate();
+        // Calculate the distance between the two points
+        double lat1 = coordinate1.getX();
+        double lon1 = coordinate1.getY();
+        double lat2 = coordinate2.getX();
+        double lon2 = coordinate2.getY();
+        GeodesicData g = Geodesic.WGS84.Inverse(lat1, lon1, lat2, lon2);
+        return g.s12;
+    }
+
+    /**
+     * Calculate the length of a geometry using the Spheroid formula.
+     * Equivalent to PostGIS ST_LengthSpheroid and PostGIS ST_Length(useSpheroid=true)
+     * WGS84 ellipsoid is used.
+     * @param geom
+     * @return
+     */
+    public static double length(Geometry geom) {
+        if (geom.getGeometryType().equals("Polygon") || geom.getGeometryType().equals("LineString")) {
+            PolygonArea p = new PolygonArea(Geodesic.WGS84, true);
+            Coordinate[] coordinates = geom.getCoordinates();
+            for (int i = 0; i < coordinates.length; i++) {
+                p.AddPoint(coordinates[i].getX(), coordinates[i].getY());
+            }
+            PolygonResult compute = p.Compute();
+            return compute.perimeter;
+        }
+        else if (geom.getGeometryType().equals("MultiPolygon") || geom.getGeometryType().equals("MultiLineString") || geom.getGeometryType().equals("GeometryCollection")) {
+            double length = 0.0;
+            for (int i = 0; i < geom.getNumGeometries(); i++) {
+                length += length(geom.getGeometryN(i));
+            }
+            return length;
+        }
+        else {
+            return 0.0;
+        }
+    }
+
+    /**
+     * Calculate the area of a geometry using the Spheroid formula.
+     * Equivalent to PostGIS ST_Area(useSpheroid=true)
+     * WGS84 ellipsoid is used.
+     * @param geom
+     * @return
+     */
+    public static double area(Geometry geom) {
+        if (geom.getGeometryType().equals("Polygon")) {
+            PolygonArea p = new PolygonArea(Geodesic.WGS84, false);
+            Coordinate[] coordinates = geom.getCoordinates();
+            for (int i = 0; i < coordinates.length; i++) {
+                p.AddPoint(coordinates[i].getX(), coordinates[i].getY());
+            }
+            PolygonResult compute = p.Compute();
+            // The area is negative if the polygon is oriented clockwise
+            // We make sure that all area are positive
+            return abs(compute.area);
+        }
+        else if (geom.getGeometryType().equals("MultiPolygon") || geom.getGeometryType().equals("GeometryCollection")) {
+            double area = 0.0;
+            for (int i = 0; i < geom.getNumGeometries(); i++) {
+                area += area(geom.getGeometryN(i));
+            }
+            return area;
+        }
+        else {
+            return 0.0;
+        }
+    }
+}
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 78a26e19..2ada6803 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -15,6 +15,8 @@ package org.apache.sedona.common;
 
 import com.google.common.geometry.S2CellId;
 import com.google.common.math.DoubleMath;
+import org.apache.sedona.common.sphere.Haversine;
+import org.apache.sedona.common.sphere.Spheroid;
 import org.apache.sedona.common.utils.S2Utils;
 import org.junit.Test;
 import org.locationtech.jts.geom.*;
@@ -26,6 +28,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 public class FunctionsTest {
     public static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
@@ -441,4 +444,125 @@ public class FunctionsTest {
         assertEquals("Median failed to converge within 1.0E-06 after 5 iterations.", e.getMessage());
     }
 
+    @Test
+    public void haversineDistance() {
+        // Basic check
+        Point p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 90));
+        Point p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0));
+        assertEquals(1.0018754171394622E7, Haversine.distance(p1, p2), 0.00000001);
+
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(51.3168, -0.56));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(55.9533, -3.1883));
+        assertEquals(544405.4459192449, Haversine.distance(p1, p2), 0.00000001);
+
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(48.353889, 11.786111));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(50.033333, 8.570556));
+        assertEquals(299407.6894786948, Haversine.distance(p1, p2), 0.00000001);
+
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(48.353889, 11.786111));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(52.559722, 13.287778));
+        assertEquals(480106.0821386384, Haversine.distance(p1, p2), 0.00000001);
+
+        LineString l1 = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 0, 90));
+        LineString l2 = GEOMETRY_FACTORY.createLineString(coordArray(0, 1, 0, 0));
+        assertEquals(4953717.340300673, Haversine.distance(l1, l2), 0.00000001);
+
+        // HK to Sydney
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(22.308919, 113.914603));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(-33.946111, 151.177222));
+        assertEquals(7402166.655938837, Haversine.distance(p1, p2), 0.00000001);
+
+        // HK to Toronto
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(22.308919, 113.914603));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(43.677223, -79.630556));
+        assertEquals(1.2562590459399283E7, Haversine.distance(p1, p2), 0.00000001);
+    }
+
+    @Test
+    public void spheroidDistance() {
+        // Basic check
+        Point p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 90));
+        Point p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0));
+        assertEquals(1.0018754171394622E7, Spheroid.distance(p1, p2), 0.00000001);
+
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(51.3168, -0.56));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(55.9533, -3.1883));
+        assertEquals(544430.9411996203, Spheroid.distance(p1, p2), 0.00000001);
+
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(48.353889, 11.786111));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(50.033333, 8.570556));
+        assertEquals(299648.07216251583, Spheroid.distance(p1, p2), 0.00000001);
+
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(48.353889, 11.786111));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(52.559722, 13.287778));
+        assertEquals(479817.9049528187, Spheroid.distance(p1, p2), 0.00000001);
+
+        LineString l1 = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 0, 90));
+        LineString l2 = GEOMETRY_FACTORY.createLineString(coordArray(0, 1, 0, 0));
+        assertEquals(4953717.340300673, Spheroid.distance(l1, l2), 0.00000001);
+
+        // HK to Sydney
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(22.308919, 113.914603));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(-33.946111, 151.177222));
+        assertEquals(7371809.8295041, Spheroid.distance(p1, p2), 0.00000001);
+
+        // HK to Toronto
+        p1 = GEOMETRY_FACTORY.createPoint(new Coordinate(22.308919, 113.914603));
+        p2 = GEOMETRY_FACTORY.createPoint(new Coordinate(43.677223, -79.630556));
+        assertEquals(1.2568775317073349E7, Spheroid.distance(p1, p2), 0.00000001);
+    }
+
+    @Test
+    public void spheroidArea() {
+        Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 90));
+        assertEquals(0, Spheroid.area(point), 0.00000001);
+
+        LineString line = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 0, 90));
+        assertEquals(0, Spheroid.area(line), 0.00000001);
+
+        Polygon polygon1 = GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 0, 90, 0, 0));
+        assertEquals(0, Spheroid.area(polygon1), 0.00000001);
+
+        Polygon polygon2 = GEOMETRY_FACTORY.createPolygon(coordArray(35, 34, 30, 28, 34, 25, 35, 34));
+        assertEquals(2.0182485081176245E11, Spheroid.area(polygon2), 0.00000001);
+
+        Polygon polygon3 = GEOMETRY_FACTORY.createPolygon(coordArray(35, 34, 34, 25, 30, 28, 35, 34));
+        assertEquals(2.0182485081176245E11, Spheroid.area(polygon3), 0.00000001);
+
+        MultiPoint multiPoint = GEOMETRY_FACTORY.createMultiPoint(new Point[] { point, point });
+        assertEquals(0, Spheroid.area(multiPoint), 0.00000001);
+
+        MultiLineString multiLineString = GEOMETRY_FACTORY.createMultiLineString(new LineString[] { line, line });
+        assertEquals(0, Spheroid.area(multiLineString), 0.00000001);
+
+        MultiPolygon multiPolygon = GEOMETRY_FACTORY.createMultiPolygon(new Polygon[] {polygon2, polygon3});
+        assertEquals(4.036497016235249E11, Spheroid.area(multiPolygon), 0.00000001);
+
+        GeometryCollection geometryCollection = GEOMETRY_FACTORY.createGeometryCollection(new Geometry[] {point, polygon2, polygon3});
+        assertEquals(4.036497016235249E11, Spheroid.area(geometryCollection), 0.00000001);
+    }
+
+    @Test
+    public void spheroidLength() {
+        Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 90));
+        assertEquals(0, Spheroid.length(point), 0.00000001);
+
+        LineString line = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 0, 90));
+        assertEquals(1.0018754171394622E7, Spheroid.length(line), 0.00000001);
+
+        Polygon polygon = GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 0, 90, 0, 0));
+        assertEquals(2.0037508342789244E7, Spheroid.length(polygon), 0.00000001);
+
+        MultiPoint multiPoint = GEOMETRY_FACTORY.createMultiPoint(new Point[] { point, point });
+        assertEquals(0, Spheroid.length(multiPoint), 0.00000001);
+
+        MultiLineString multiLineString = GEOMETRY_FACTORY.createMultiLineString(new LineString[] { line, line });
+        assertEquals(2.0037508342789244E7, Spheroid.length(multiLineString), 0.00000001);
+
+        MultiPolygon multiPolygon = GEOMETRY_FACTORY.createMultiPolygon(new Polygon[] {polygon, polygon});
+        assertEquals(4.007501668557849E7, Spheroid.length(multiPolygon), 0.00000001);
+
+        GeometryCollection geometryCollection = GEOMETRY_FACTORY.createGeometryCollection(new Geometry[] {point, line, multiLineString});
+        assertEquals(3.0056262514183864E7, Spheroid.length(geometryCollection), 0.00000001);
+    }
 }
diff --git a/pom.xml b/pom.xml
index 7c7aa490..3a602ebc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,6 +85,7 @@
         <googles2.version>2.0.0</googles2.version>
         <scalatest.version>3.1.1</scalatest.version>
         <scala-collection-compat.version>2.5.0</scala-collection-compat.version>
+        <geoglib.version>1.52</geoglib.version>
 
         <geotools.scope>provided</geotools.scope>
         <!-- Because it's not in Maven central, make it provided by default -->
@@ -183,6 +184,10 @@
                         <groupId>com.fasterxml.jackson.core</groupId>
                         <artifactId>*</artifactId>
                     </exclusion>
+                    <exclusion>
+                        <groupId>net.sf.geographiclib</groupId>
+                        <artifactId>*</artifactId>
+                    </exclusion>
                 </exclusions>
             </dependency>
             <!--for CRS transformation-->
@@ -296,6 +301,11 @@
                 <artifactId>s2-geometry</artifactId>
                 <version>${googles2.version}</version>
             </dependency>
+            <dependency>
+                <groupId>net.sf.geographiclib</groupId>
+                <artifactId>GeographicLib-Java</artifactId>
+                <version>${geoglib.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.mockito</groupId>
                 <artifactId>mockito-inline</artifactId>
diff --git a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 9b85f931..0842f467 100644
--- a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -143,6 +143,10 @@ object Catalog {
     function[ST_Split](),
     function[ST_S2CellIDs](),
     function[ST_GeometricMedian](1e-6, 1000, false),
+    function[ST_DistanceSphere](6378137.0),
+    function[ST_DistanceSpheroid](),
+    function[ST_AreaSpheroid](),
+    function[ST_LengthSpheroid](),
     // Expression for rasters
     function[RS_NormalizedDifference](),
     function[RS_Mean](),
diff --git a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 7298e1af..0a4c0dcb 100644
--- a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -19,6 +19,7 @@
 package org.apache.spark.sql.sedona_sql.expressions
 
 import org.apache.sedona.common.Functions
+import org.apache.sedona.common.sphere.{Haversine, Spheroid}
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
 import org.apache.spark.sql.catalyst.expressions.{Expression, Generator}
@@ -948,3 +949,35 @@ case class ST_GeometricMedian(inputExpressions: Seq[Expression])
   }
 }
 
+case class ST_DistanceSphere(inputExpressions: Seq[Expression])
+  extends InferredTernaryExpression(Haversine.distance) with FoldableExpression {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
+
+case class ST_DistanceSpheroid(inputExpressions: Seq[Expression])
+  extends InferredBinaryExpression(Spheroid.distance) with FoldableExpression {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
+
+case class ST_AreaSpheroid(inputExpressions: Seq[Expression])
+  extends InferredUnaryExpression(Spheroid.area) with FoldableExpression {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
+
+case class ST_LengthSpheroid(inputExpressions: Seq[Expression])
+  extends InferredUnaryExpression(Spheroid.length) with FoldableExpression {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
+
diff --git a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
index 3e5fa706..f7a56bf8 100644
--- a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
+++ b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -285,4 +285,20 @@ object st_functions extends DataFrameAPI {
   def ST_GeometricMedian(geometry: Column, tolerance: Column, maxIter: Column, failIfNotConverged: Column): Column = wrapExpression[ST_GeometricMedian](geometry, tolerance, maxIter, failIfNotConverged)
   def ST_GeometricMedian(geometry: String, tolerance: Double, maxIter: Int, failIfNotConverged: Boolean): Column = wrapExpression[ST_GeometricMedian](geometry, tolerance, maxIter, failIfNotConverged)
 
+  def ST_DistanceSphere(a: Column, b: Column): Column = wrapExpression[ST_DistanceSphere](a, b, 6378137.0)
+  def ST_DistanceSphere(a: String, b: String): Column = wrapExpression[ST_DistanceSphere](a, b, 6378137.0)
+  def ST_DistanceSphere(a: Column, b: Column, c: Column): Column = wrapExpression[ST_DistanceSphere](a, b, c)
+  def ST_DistanceSphere(a: String, b: String, c: Double): Column = wrapExpression[ST_DistanceSphere](a, b, c)
+
+  def ST_DistanceSpheroid(a: Column, b: Column): Column = wrapExpression[ST_DistanceSpheroid](a, b)
+
+  def ST_DistanceSpheroid(a: String, b: String): Column = wrapExpression[ST_DistanceSpheroid](a, b)
+
+  def ST_AreaSpheroid(a: Column): Column = wrapExpression[ST_AreaSpheroid](a)
+
+  def ST_AreaSpheroid(a: String): Column = wrapExpression[ST_AreaSpheroid](a)
+
+  def ST_LengthSpheroid(a: Column): Column = wrapExpression[ST_LengthSpheroid](a)
+
+  def ST_LengthSpheroid(a: String): Column = wrapExpression[ST_LengthSpheroid](a)
 }
diff --git a/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala b/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
index db11c415..75338628 100644
--- a/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
+++ b/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -911,5 +911,41 @@ class dataFrameAPITestScala extends TestBaseScala {
       val mbrResult = dfMRB.take(1)(0).getAs[mutable.WrappedArray[Long]](0).toSet
       assert (actualResult.subsetOf(mbrResult))
     }
+
+    it("Passed ST_DistanceSphere") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POINT (0 0)') AS geom1, ST_GeomFromWKT('POINT (0 90)') AS geom2")
+      var df = baseDf.select(ST_DistanceSphere("geom1", "geom2"))
+      var actualResult = df.take(1)(0).getDouble(0)
+      val expectedResult = 10018754.171394622
+      assert(actualResult == expectedResult)
+
+      df = baseDf.select(ST_DistanceSphere("geom1", "geom2", 6378137.0))
+      actualResult = df.take(1)(0).getDouble(0)
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_DistanceSpheroid") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POINT (0 0)') AS geom1, ST_GeomFromWKT('POINT (0 90)') AS geom2")
+      val df = baseDf.select(ST_DistanceSpheroid("geom1", "geom2"))
+      val actualResult = df.take(1)(0).getDouble(0)
+      val expectedResult = 10018754.171394622
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_AreaSpheroid") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('Polygon ((35 34, 30 28, 34 25, 35 34))') AS geom")
+      val df = baseDf.select(ST_AreaSpheroid("geom"))
+      val actualResult = df.take(1)(0).getDouble(0)
+      val expectedResult = 201824850811.76245
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_LengthSpheroid") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LineString (0 0, 0 90)') AS geom")
+      val df = baseDf.select(ST_LengthSpheroid("geom"))
+      val actualResult = df.take(1)(0).getDouble(0)
+      val expectedResult = 10018754.171394622
+      assert(actualResult == expectedResult)
+    }
   }
 }
diff --git a/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala b/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index a66a9be2..92aaafc8 100644
--- a/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -24,8 +24,9 @@ import org.apache.sedona.sql.implicits._
 import org.apache.spark.sql.functions._
 import org.apache.spark.sql.{DataFrame, Row}
 import org.geotools.referencing.CRS
+import org.junit.Assert.assertEquals
 import org.locationtech.jts.algorithm.MinimumBoundingCircle
-import org.locationtech.jts.geom.{CoordinateSequenceComparator, Geometry, Polygon}
+import org.locationtech.jts.geom.{Geometry, Polygon}
 import org.locationtech.jts.io.WKTWriter
 import org.locationtech.jts.linearref.LengthIndexedLine
 import org.locationtech.jts.operation.distance3d.Distance3DOp
@@ -1839,4 +1840,73 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
     }
   }
 
+  it("Should pass ST_DistanceSphere") {
+    val geomTestCases = Map(
+      ("'POINT (51.3168 -0.56)'", "'POINT (55.9533 -3.1883)'") -> "544405.4459192449",
+      ("'LineString (0 0, 0 90)'", "'LineString (0 1, 0 0)'") -> "4953717.340300673"
+    )
+    for (((geom1, geom2), expectedResult) <- geomTestCases) {
+      val df = sparkSession.sql(s"SELECT ST_DistanceSphere(ST_GeomFromWKT($geom1), ST_GeomFromWKT($geom2)), " +
+        s"$expectedResult")
+      val actual = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expected = df.take(1)(0).get(1).asInstanceOf[java.math.BigDecimal].doubleValue()
+      assertEquals(expected, actual, 0.000001)
+    }
+
+    val geomTestCases2 = Map(
+      ("'POINT (51.3168 -0.56)'", "'POINT (55.9533 -3.1883)'", "6378137") -> "544405.4459192449",
+      ("'LineString (0 0, 0 90)'", "'LineString (0 1, 0 0)'", "6378137.0") -> "4953717.340300673"
+    )
+    for (((geom1, geom2, radius), expectedResult) <- geomTestCases2) {
+      val df = sparkSession.sql(s"SELECT ST_DistanceSphere(ST_GeomFromWKT($geom1), ST_GeomFromWKT($geom2), $radius), " +
+        s"$expectedResult")
+      val actual = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expected = df.take(1)(0).get(1).asInstanceOf[java.math.BigDecimal].doubleValue()
+      assertEquals(expected, actual, 0.000001)
+    }
+  }
+
+  it("Should pass ST_DistanceSpheroid") {
+    val geomTestCases = Map(
+      ("'POINT (51.3168 -0.56)'", "'POINT (55.9533 -3.1883)'") -> "544430.94119962039",
+      ("'LineString (0 0, 0 90)'", "'LineString (0 1, 0 0)'") -> "4953717.340300673"
+    )
+    for (((geom1, geom2), expectedResult) <- geomTestCases) {
+      val df = sparkSession.sql(s"SELECT ST_DistanceSpheroid(ST_GeomFromWKT($geom1), ST_GeomFromWKT($geom2)), " +
+        s"$expectedResult")
+      val actual = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expected = df.take(1)(0).get(1).asInstanceOf[java.math.BigDecimal].doubleValue()
+      assertEquals(expected, actual, 0.000001)
+    }
+  }
+
+  it("Should pass ST_AreaSpheroid") {
+    val geomTestCases = Map(
+      ("'POINT (51.3168 -0.56)'") -> "0.0",
+      ("'LineString (0 0, 0 90)'") -> "0.0",
+      ("'Polygon ((35 34, 30 28, 34 25, 35 34))'") -> "201824850811.76245"
+    )
+    for (((geom1), expectedResult) <- geomTestCases) {
+      val df = sparkSession.sql(s"SELECT ST_AreaSpheroid(ST_GeomFromWKT($geom1)), " +
+        s"$expectedResult")
+      val actual = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expected = df.take(1)(0).get(1).asInstanceOf[java.math.BigDecimal].doubleValue()
+      assertEquals(expected, actual, 0.000001)
+    }
+  }
+
+  it("Should pass ST_LengthSpheroid") {
+    val geomTestCases = Map(
+      ("'POINT (51.3168 -0.56)'") -> "0.0",
+      ("'LineString (0 0, 0 90)'") -> "10018754.17139462",
+      ("'Polygon ((0 0, 0 90, 0 0))'") -> "20037508.34278924"
+    )
+    for (((geom1), expectedResult) <- geomTestCases) {
+      val df = sparkSession.sql(s"SELECT ST_LengthSpheroid(ST_GeomFromWKT($geom1)), " +
+        s"$expectedResult")
+      val actual = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expected = df.take(1)(0).get(1).asInstanceOf[java.math.BigDecimal].doubleValue()
+      assertEquals(expected, actual, 0.000001)
+    }
+  }
 }