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 23f1b80e [SEDONA-281] Support geodesic / geography functions (#835)
23f1b80e is described below

commit 23f1b80e85cfe02b9f0fc9c9c1460d335936866f
Author: Jia Yu <[email protected]>
AuthorDate: Sat May 20 09:35:33 2023 -0700

    [SEDONA-281] Support geodesic / geography functions (#835)
---
 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 +++++++++++++++++++++
 docs/api/flink/Function.md                         |  78 +++++++++++++
 docs/api/sql/Function.md                           |  77 +++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |   4 +
 .../apache/sedona/flink/expressions/Functions.java |  44 ++++++++
 .../java/org/apache/sedona/flink/FunctionTest.java |  45 ++++++++
 pom.xml                                            |  10 ++
 python/sedona/sql/st_functions.py                  |  52 +++++++++
 python/tests/sql/test_dataframe_api.py             |   5 +
 .../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  |  37 ++++++
 .../org/apache/sedona/sql/functionTestScala.scala  |  72 +++++++++++-
 17 files changed, 784 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..b81e85a6 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.1);
+
+        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.1);
+
+        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.1);
+
+        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.1);
+
+        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.1);
+
+        // 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.1);
+
+        // 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.1);
+    }
+
+    @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.1);
+
+        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.1);
+
+        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.1);
+
+        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.1);
+
+        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.1);
+
+        // 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.1);
+
+        // 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.1);
+    }
+
+    @Test
+    public void spheroidArea() {
+        Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 90));
+        assertEquals(0, Spheroid.area(point), 0.1);
+
+        LineString line = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 
0, 90));
+        assertEquals(0, Spheroid.area(line), 0.1);
+
+        Polygon polygon1 = GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 0, 
90, 0, 0));
+        assertEquals(0, Spheroid.area(polygon1), 0.1);
+
+        Polygon polygon2 = GEOMETRY_FACTORY.createPolygon(coordArray(35, 34, 
30, 28, 34, 25, 35, 34));
+        assertEquals(2.0182485081176245E11, Spheroid.area(polygon2), 0.1);
+
+        Polygon polygon3 = GEOMETRY_FACTORY.createPolygon(coordArray(35, 34, 
34, 25, 30, 28, 35, 34));
+        assertEquals(2.0182485081176245E11, Spheroid.area(polygon3), 0.1);
+
+        MultiPoint multiPoint = GEOMETRY_FACTORY.createMultiPoint(new Point[] 
{ point, point });
+        assertEquals(0, Spheroid.area(multiPoint), 0.1);
+
+        MultiLineString multiLineString = 
GEOMETRY_FACTORY.createMultiLineString(new LineString[] { line, line });
+        assertEquals(0, Spheroid.area(multiLineString), 0.1);
+
+        MultiPolygon multiPolygon = GEOMETRY_FACTORY.createMultiPolygon(new 
Polygon[] {polygon2, polygon3});
+        assertEquals(4.036497016235249E11, Spheroid.area(multiPolygon), 0.1);
+
+        GeometryCollection geometryCollection = 
GEOMETRY_FACTORY.createGeometryCollection(new Geometry[] {point, polygon2, 
polygon3});
+        assertEquals(4.036497016235249E11, Spheroid.area(geometryCollection), 
0.1);
+    }
+
+    @Test
+    public void spheroidLength() {
+        Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 90));
+        assertEquals(0, Spheroid.length(point), 0.1);
+
+        LineString line = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 
0, 90));
+        assertEquals(1.0018754171394622E7, Spheroid.length(line), 0.1);
+
+        Polygon polygon = GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 0, 
90, 0, 0));
+        assertEquals(2.0037508342789244E7, Spheroid.length(polygon), 0.1);
+
+        MultiPoint multiPoint = GEOMETRY_FACTORY.createMultiPoint(new Point[] 
{ point, point });
+        assertEquals(0, Spheroid.length(multiPoint), 0.1);
+
+        MultiLineString multiLineString = 
GEOMETRY_FACTORY.createMultiLineString(new LineString[] { line, line });
+        assertEquals(2.0037508342789244E7, Spheroid.length(multiLineString), 
0.1);
+
+        MultiPolygon multiPolygon = GEOMETRY_FACTORY.createMultiPolygon(new 
Polygon[] {polygon, polygon});
+        assertEquals(4.007501668557849E7, Spheroid.length(multiPolygon), 0.1);
+
+        GeometryCollection geometryCollection = 
GEOMETRY_FACTORY.createGeometryCollection(new Geometry[] {point, line, 
multiLineString});
+        assertEquals(3.0056262514183864E7, 
Spheroid.length(geometryCollection), 0.1);
+    }
 }
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 603609d8..813ed1c1 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. For non-point data, we 
first take the centroids of both geometries and then compute the distance.
+
+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. For non-point 
data, we first take the centroids of both geometries and then compute the 
distance.
+
+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..b8643268 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. For non-point 
data, we first take the centroids of both geometries and then compute the 
distance.
+
+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. For non-point 
data, we first take the centroids of both geometries and then compute the 
distance.
+
+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.
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..f04328e0 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, 0.1);
+    }
+
     @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, 0.1);
+    }
+
+    @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, 0.1);
+    }
+
+    @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, 0.1);
+    }
+
     @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, 0.1);
+    }
+
     @Test
     public void testYMax() {
         Table polygonTable = createPolygonTable(1);
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/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..6551d3bd 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)"),
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..24a3d2b8 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
@@ -26,6 +26,7 @@ import 
org.apache.spark.sql.sedona_sql.expressions.st_constructors._
 import org.apache.spark.sql.sedona_sql.expressions.st_functions._
 import org.apache.spark.sql.sedona_sql.expressions.st_predicates._
 import org.apache.spark.sql.sedona_sql.expressions.st_aggregates._
+import org.junit.Assert.assertEquals
 
 import scala.collection.mutable
 
@@ -911,5 +912,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)
+      assertEquals(expectedResult, actualResult, 0.1)
+    }
+
+    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
+      assertEquals(expectedResult, actualResult, 0.1)
+    }
+
+    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
+      assertEquals(expectedResult, actualResult, 0.1)
+    }
+
+    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
+      assertEquals(expectedResult, actualResult, 0.1)
+    }
   }
 }
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..a3336ca1 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.1)
+    }
+
+    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.1)
+    }
+  }
+
+  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.1)
+    }
+  }
+
+  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.1)
+    }
+  }
+
+  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.1)
+    }
+  }
 }

Reply via email to