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)
+ }
+ }
}