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 1c0890f40b [GH-2302] add constructor of Geometry to Geography (#2298)
1c0890f40b is described below
commit 1c0890f40b3a14446867eb9f752a810867b6123a
Author: Zhuocheng Shang <[email protected]>
AuthorDate: Fri Aug 22 00:39:49 2025 -0700
[GH-2302] add constructor of Geometry to Geography (#2298)
* add constructor of Geometry to Geography
* add constructors ST_GeomToGeography
* correct GeomToGeography constructors
* empty commit to retrigger ci
* fix handle Null and duplicates linestrings
* clean file format
* Update
spark/common/src/test/scala/org/apache/sedona/sql/geography/ConstructorsDataFrameAPITest.scala
Co-authored-by: Copilot <[email protected]>
* Update docs/api/sql/geography/Constructor.md
Co-authored-by: Copilot <[email protected]>
* Update
common/src/main/java/org/apache/sedona/common/geography/Constructors.java
Co-authored-by: Copilot <[email protected]>
* Remove exception type that does make sense from function signature
* Fix the doc
---------
Co-authored-by: Jia Yu <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Kristin Cowalcijk <[email protected]>
Co-authored-by: Jia Yu <[email protected]>
---
.../sedona/common/geography/Constructors.java | 196 +++++++++++++++++++++
.../sedona/common/Geography/ConstructorsTest.java | 105 +++++++++++
docs/api/sql/geography/Constructor.md | 44 ++++-
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 5 +-
.../expressions/geography/Constructors.scala | 15 ++
.../sedona_sql/expressions/st_constructors.scala | 5 +-
.../geography/ConstructorsDataFrameAPITest.scala | 10 ++
.../sedona/sql/geography/ConstructorsTest.scala | 10 ++
8 files changed, 386 insertions(+), 4 deletions(-)
diff --git
a/common/src/main/java/org/apache/sedona/common/geography/Constructors.java
b/common/src/main/java/org/apache/sedona/common/geography/Constructors.java
index 7df27d18b0..47ee74ef44 100644
--- a/common/src/main/java/org/apache/sedona/common/geography/Constructors.java
+++ b/common/src/main/java/org/apache/sedona/common/geography/Constructors.java
@@ -264,4 +264,200 @@ public class Constructors {
}
return gf.createGeometryCollection(gs);
}
+
+ public static Geography geomToGeography(Geometry geom) {
+ if (geom == null) {
+ return null;
+ }
+ Geography geography;
+ if (geom instanceof Point) {
+ geography = pointToGeog((Point) geom);
+ } else if (geom instanceof MultiPoint) {
+ geography = mPointToGeog((MultiPoint) geom);
+ } else if (geom instanceof LineString) {
+ geography = lineToGeog((LineString) geom);
+ } else if (geom instanceof MultiLineString) {
+ geography = mLineToGeog((MultiLineString) geom);
+ } else if (geom instanceof Polygon) {
+ geography = polyToGeog((Polygon) geom);
+ } else if (geom instanceof MultiPolygon) {
+ geography = mPolyToGeog((MultiPolygon) geom);
+ } else if (geom instanceof GeometryCollection) {
+ geography = geomCollToGeog((GeometryCollection) geom);
+ } else {
+ throw new UnsupportedOperationException(
+ "Geometry type is not supported: " +
geom.getClass().getSimpleName());
+ }
+ geography.setSRID(geom.getSRID());
+ return geography;
+ }
+
+ private static Geography pointToGeog(Point geom) throws
IllegalArgumentException {
+ Coordinate[] pts = geom.getCoordinates();
+ if (pts.length == 0 || Double.isNaN(pts[0].x) || Double.isNaN(pts[0].y)) {
+ return new SinglePointGeography(); // Return empty geography
+ }
+ double lon = pts[0].x;
+ double lat = pts[0].y;
+
+ // Just create the point directly. No builder needed.
+ S2Point s2Point = S2LatLng.fromDegrees(lat, lon).toPoint();
+ return new SinglePointGeography(s2Point);
+ }
+
+ private static Geography mPointToGeog(MultiPoint geom) throws
IllegalArgumentException {
+ Coordinate[] pts = geom.getCoordinates();
+ List<S2Point> points = toS2Points(pts);
+ // Build via S2Builder + S2PointVectorLayer
+ S2Builder builder = new S2Builder.Builder().build();
+ S2PointVectorLayer layer = new S2PointVectorLayer();
+ builder.startLayer(layer);
+ // must call build() before reading out the points
+ for (S2Point pt : points) {
+ builder.addPoint(pt);
+ }
+ S2Error error = new S2Error();
+ if (!builder.build(error)) {
+ throw new IllegalArgumentException("Failed to build S2 point layer: " +
error.text());
+ }
+ return new PointGeography(layer.getPointVector());
+ }
+
+ private static Geography lineToGeog(LineString geom) throws
IllegalArgumentException {
+ // Build S2 points
+ List<S2Point> pts = toS2Points(geom.getCoordinates());
+ if (pts.size() < 2) {
+ // empty or degenerate → empty single polyline
+ return new SinglePolylineGeography();
+ }
+
+ S2Builder builder = new S2Builder.Builder().build();
+ S2PolylineLayer layer = new S2PolylineLayer();
+ builder.startLayer(layer);
+ builder.addPolyline(new S2Polyline(pts));
+
+ S2Error error = new S2Error();
+ if (!builder.build(error)) {
+ throw new IllegalArgumentException("Failed to build S2 polyline: " +
error.text());
+ }
+ S2Polyline s2poly = layer.getPolyline();
+ return new SinglePolylineGeography(s2poly);
+ }
+
+ private static Geography mLineToGeog(MultiLineString geom) throws
IllegalArgumentException {
+ S2Builder builder = new S2Builder.Builder().build();
+ S2PolylineVectorLayer.Options opts =
+ new S2PolylineVectorLayer.Options()
+ .setDuplicateEdges(
+ S2Builder.GraphOptions.DuplicateEdges.MERGE); // reject
duplicate segments
+ S2PolylineVectorLayer vectorLayer = new S2PolylineVectorLayer(opts);
+ builder.startLayer(vectorLayer);
+ for (int i = 0; i < geom.getNumGeometries(); i++) {
+ LineString ls = (LineString) geom.getGeometryN(i);
+ List<S2Point> pts = toS2Points(ls.getCoordinates());
+ if (pts.size() >= 2) {
+ builder.addPolyline(new S2Polyline(pts));
+ }
+ // Empty/degenerate parts are skipped
+ }
+ S2Error error = new S2Error();
+ if (!builder.build(error)) {
+ throw new IllegalArgumentException("Failed to build S2 polyline: " +
error.text());
+ }
+ return new PolylineGeography(vectorLayer.getPolylines());
+ }
+
+ private static Geography polyToGeog(Polygon geom) throws
IllegalArgumentException {
+ // Construct S2 polygon (parity handles holes automatically)
+ S2Polygon s2poly = toS2Polygon(geom);
+ return new PolygonGeography(s2poly);
+ }
+
+ private static Geography mPolyToGeog(MultiPolygon geom) throws
IllegalArgumentException {
+ final List<S2Polygon> polys = new ArrayList<>();
+ for (int i = 0; i < geom.getNumGeometries(); i++) {
+ Polygon p = (Polygon) geom.getGeometryN(i);
+ S2Polygon s2 = toS2Polygon(p);
+ if (s2 != null && !s2.isEmpty()) polys.add(s2);
+ }
+
+ return new MultiPolygonGeography(
+ Geography.GeographyKind.MULTIPOLYGON,
Collections.unmodifiableList(polys));
+ }
+
+ private static Geography geomCollToGeog(GeometryCollection geom) {
+ List<Geography> features = new ArrayList<>();
+ for (int i = 0; i < geom.getNumGeometries(); i++) {
+ Geometry g = geom.getGeometryN(i);
+ Geography sub = geomToGeography(g);
+ if (sub != null) features.add(sub);
+ }
+ return new GeographyCollection(features);
+ }
+
+ /** Convert JTS coordinates to S2 points; drops NaNs and consecutive
duplicates. */
+ private static List<S2Point> toS2Points(Coordinate[] coords) throws
IllegalArgumentException {
+ List<S2Point> points = new ArrayList<>(coords.length);
+ for (int i = 0; i < coords.length; i++) {
+ double lon = coords[i].x;
+ double lat = coords[i].y;
+ // 1. Check for and drop NaNs.
+ if (Double.isNaN(lat) || Double.isNaN(lon)) {
+ continue;
+ }
+ S2Point s2Point = S2LatLng.fromDegrees(lat, lon).toPoint();
+ // 2. Check for and drop consecutive duplicates.
+ if (!points.isEmpty() && points.get(points.size() - 1).equals(s2Point)) {
+ continue;
+ }
+ points.add(s2Point);
+ }
+ return points;
+ }
+
+ /** Convert a JTS LinearRing to a normalized S2Loop. Returns null if < 3
distinct vertices. */
+ private static S2Loop toS2Loop(LinearRing ring) throws
IllegalArgumentException {
+ Coordinate[] coords = ring.getCoordinates();
+ if (coords == null || coords.length < 4) { // JTS rings usually have
first==last
+ return null;
+ }
+
+ List<S2Point> pts = toS2Points(coords);
+
+ if (pts.size() < 3) return null;
+ if (pts.get(0).equals(pts.get(pts.size() - 1))) {
+ pts.remove(pts.size() - 1); // Remove duplicate closing point if it
exists
+ }
+
+ S2Loop loop = new S2Loop(pts);
+ loop.normalize(); // ensure area <= 2π; orientation consistent for S2
+ return loop;
+ }
+
+ private static S2Polygon toS2Polygon(Polygon poly) throws
IllegalArgumentException {
+ List<S2Loop> loops = new ArrayList<>();
+ S2Loop shell = toS2Loop((LinearRing) poly.getExteriorRing());
+ if (shell != null) loops.add(shell);
+ for (int i = 0; i < poly.getNumInteriorRing(); i++) {
+ S2Loop hole = toS2Loop((LinearRing) poly.getInteriorRingN(i));
+ if (hole != null) loops.add(hole);
+ }
+ if (loops.isEmpty()) return null;
+ // Now feed those loops into S2Builder + S2PolygonLayer:
+ S2Builder builder = new S2Builder.Builder().build();
+ S2PolygonLayer polyLayer = new S2PolygonLayer();
+ builder.startLayer(polyLayer);
+ // add shell + holes
+ for (S2Loop loop : loops) {
+ builder.addLoop(loop);
+ }
+ // build
+ S2Error error = new S2Error();
+ if (!builder.build(error)) {
+ throw new IllegalArgumentException("S2Builder failed: " + error.text());
+ }
+
+ // extract the stitched polygon
+ return polyLayer.getPolygon(); // even–odd handles holes
+ }
}
diff --git
a/common/src/test/java/org/apache/sedona/common/Geography/ConstructorsTest.java
b/common/src/test/java/org/apache/sedona/common/Geography/ConstructorsTest.java
index 716b5dcac3..7fa19c0ce5 100644
---
a/common/src/test/java/org/apache/sedona/common/Geography/ConstructorsTest.java
+++
b/common/src/test/java/org/apache/sedona/common/Geography/ConstructorsTest.java
@@ -245,4 +245,109 @@ public class ConstructorsTest {
assertEquals(expected, got.toString());
assertEquals(0, got.getSRID());
}
+
+ @Test
+ public void MultiPolygonGeomToGeography() throws Exception {
+ String wkt =
+ "MULTIPOLYGON ("
+ +
+ // Component A: outer shell + lake
+ "((10 10, 70 10, 70 70, 10 70, 10 10),"
+ + " (20 20, 60 20, 60 60, 20 60, 20 20)),"
+ +
+ // Component B: island with a pond
+ " ((30 30, 50 30, 50 50, 30 50, 30 30),"
+ + " (36 36, 44 36, 44 44, 36 44, 36 36))"
+ + ")";
+ Geometry g = new org.locationtech.jts.io.WKTReader().read(wkt);
+ g.setSRID(4326);
+ Geography got = Constructors.geomToGeography(g);
+ String expected = "SRID=4326; " + wkt;
+ assertEquals(4326, got.getSRID());
+ org.locationtech.jts.io.WKTWriter wktWriter = new
org.locationtech.jts.io.WKTWriter();
+ wktWriter.setPrecisionModel(new PrecisionModel(PrecisionModel.FIXED));
+ assertEquals(expected, got.toString());
+ }
+
+ @Test
+ public void PointGeomToGeography() throws Exception {
+ Geometry geom = org.apache.sedona.common.Constructors.geomFromWKT("POINT
(1 1)", 0);
+ Geography got = Constructors.geomToGeography(geom);
+ org.locationtech.jts.io.WKTWriter wktWriter = new
org.locationtech.jts.io.WKTWriter();
+ wktWriter.setPrecisionModel(new PrecisionModel(PrecisionModel.FIXED));
+ assertEquals(geom.toString(), got.toString());
+
+ geom =
+ org.apache.sedona.common.Constructors.geomFromWKT(
+ "MULTIPOINT ((10 10), (20 20), (30 30))", 0);
+ got = Constructors.geomToGeography(geom);
+ assertEquals(geom.toString(), got.toString());
+ }
+
+ @Test
+ public void PointGeomToGeographyDuplicate() throws Exception {
+ Geometry geom =
+ org.apache.sedona.common.Constructors.geomFromWKT(
+ "MULTIPOINT ((10 10), (20 20), (20 20), (30 30))", 0);
+ Geography got = Constructors.geomToGeography(geom);
+ org.locationtech.jts.io.WKTWriter wktWriter = new
org.locationtech.jts.io.WKTWriter();
+ wktWriter.setPrecisionModel(new PrecisionModel(PrecisionModel.FIXED));
+ assertEquals("MULTIPOINT ((10 10), (20 20), (30 30))", got.toString());
+
+ geom =
+ org.apache.sedona.common.Constructors.geomFromWKT(
+ "MULTIPOINT ((10 10), (20 20), (30 30), (20 20), (10 10))", 0);
+ got = Constructors.geomToGeography(geom);
+ assertEquals("MULTIPOINT ((10 10), (20 20), (30 30))", got.toString());
+ }
+
+ @Test
+ public void LineGeomToGeography() throws Exception {
+ Geometry geom =
+ org.apache.sedona.common.Constructors.geomFromWKT("LINESTRING (1 2, 3
4, 5 6)", 0);
+ Geography got = Constructors.geomToGeography(geom);
+ org.locationtech.jts.io.WKTWriter wktWriter = new
org.locationtech.jts.io.WKTWriter();
+ wktWriter.setPrecisionModel(new PrecisionModel(PrecisionModel.FIXED));
+ assertEquals(geom.toString(), got.toString());
+
+ geom =
+ org.apache.sedona.common.Constructors.geomFromWKT(
+ "MULTILINESTRING((1 2, 3 4), (4 5, 6 7))", 0);
+ got = Constructors.geomToGeography(geom);
+ assertEquals(geom.toString(), got.toString());
+ }
+
+ @Test
+ public void LineGeomToGeographyDuplicate() throws Exception {
+ Geometry geom =
+ org.apache.sedona.common.Constructors.geomFromWKT("LINESTRING (1 2, 3
4, 3 4, 5 6)", 0);
+ Geography got = Constructors.geomToGeography(geom);
+ org.locationtech.jts.io.WKTWriter wktWriter = new
org.locationtech.jts.io.WKTWriter();
+ wktWriter.setPrecisionModel(new PrecisionModel(PrecisionModel.FIXED));
+ assertEquals("LINESTRING (1 2, 3 4, 5 6)", got.toString());
+
+ geom =
+ org.apache.sedona.common.Constructors.geomFromWKT(
+ "MULTILINESTRING ((1 2, 3 4), (4 5, 6 7), (1 2, 3 4))", 0);
+ got = Constructors.geomToGeography(geom);
+ assertEquals("MULTILINESTRING ((1 2, 3 4), (4 5, 6 7))", got.toString());
+
+ geom =
+ org.apache.sedona.common.Constructors.geomFromWKT(
+ "MULTILINESTRING ((1 2, 3 4), EMPTY, (4 5, 6 7), (1 2, 3 4))", 0);
+ got = Constructors.geomToGeography(geom);
+ assertEquals("MULTILINESTRING ((1 2, 3 4), (4 5, 6 7))", got.toString());
+ }
+
+ @Test
+ public void CollGeomToGeography() throws Exception {
+ Geometry geom =
+ org.apache.sedona.common.Constructors.geomFromWKT(
+ "GEOMETRYCOLLECTION (POINT (10 20), LINESTRING (30 40, 50 60, 70
80), POLYGON ((10 10, 20 20, 10 20, 10 10)))",
+ 0);
+ Geography got = Constructors.geomToGeography(geom);
+ org.locationtech.jts.io.WKTWriter wktWriter = new
org.locationtech.jts.io.WKTWriter();
+ wktWriter.setPrecisionModel(new PrecisionModel(PrecisionModel.FIXED));
+ assertEquals(geom.toString(), got.toString());
+ }
}
diff --git a/docs/api/sql/geography/Constructor.md
b/docs/api/sql/geography/Constructor.md
index 4b3ae2331b..1845ea9174 100644
--- a/docs/api/sql/geography/Constructor.md
+++ b/docs/api/sql/geography/Constructor.md
@@ -143,7 +143,21 @@ SRID=4326; LINESTRING (0 0, 3 3, 4 4)
## ST_GeogToGeometry
-Introduction: Construct a Geometry from a Geography.
+Introduction:
+
+This function constructs a planar Geometry object from a Geography. While
Sedona makes every effort to preserve the original spatial object, the
conversion is not always exact because Geography and Geometry have different
underlying models:
+
+* Geography represents shapes on the Earth’s surface (spherical).
+* Geometry represents shapes on a flat, Euclidean plane.
+
+This difference can cause certain ambiguities during conversion. For example:
+
+* A polygon in Geography always refers to the region on the Earth’s surface
that the ring encloses. When converted to Geometry, however, it becomes unclear
whether the polygon is intended to represent the “inside” or its complement
(the “outside”) on the sphere.
+* Long edges that cross the antimeridian or cover poles may also be
represented differently once projected into planar space.
+
+In practice, Sedona preserves the coordinates and ring orientation as closely
as possible, but you should be aware that some topological properties (e.g.,
area, distance) may not match exactly after conversion.
+
+Sedona does not validate or enforce the SRID of the input Geography. Whatever
SRID is attached to the Geography will be carried over to the resulting
Geometry, even if it is not appropriate for planar interpretation. It is the
user’s responsibility to ensure that the Geography’s SRID is meaningful in the
target Geometry context.
Format:
@@ -162,3 +176,31 @@ Output:
```
MULTILINESTRING ((90 90, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))
```
+
+## ST_GeomToGeography
+
+Introduction:
+
+This function constructs a Geography object from a planar Geometry. This
function is intended for geometries defined in a Geographic Coordinate
Reference System (CRS), most commonly WGS84 (EPSG:4326), where coordinates are
expressed in degrees and the longitude/latitude order
+
+If the input Geometry is defined in a projected CRS (e.g., Web Mercator
EPSG:3857, UTM zones), the conversion may succeed syntactically, but the
resulting Geography will not be meaningful. This is because Geography
interprets coordinates on the surface of a sphere, not a flat plane.
+
+Sedona does not validate or enforce the SRID of the input Geometry. Whatever
SRID is attached to the Geometry will simply be carried over to the Geography,
even if it is inappropriate for spherical interpretation. It is the user’s
responsibility to ensure the input Geometry uses a Geographic CRS.
+
+Format:
+
+`ST_GeomToGeography (geom: Geometry)`
+
+Since: `v1.8.0`
+
+SQL example:
+
+```sql
+SELECT ST_GeomToGeography(ST_GeomFromWKT('MULTIPOLYGON (((10 10, 70 10, 70 70,
10 70, 10 10), (20 20, 60 20, 60 60, 20 60, 20 20)), ((30 30, 50 30, 50 50, 30
50, 30 30), (36 36, 44 36, 44 44, 36 44, 36 36)))'))
+```
+
+Output:
+
+```
+MULTIPOLYGON (((10 10, 70 10, 70 70, 10 70, 10 10), (20 20, 60 20, 60 60, 20
60, 20 20)), ((30 30, 50 30, 50 50, 30 50, 30 30), (36 36, 44 36, 44 44, 36 44,
36 36)))
+```
diff --git
a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index d4833a24e4..42f9859b12 100644
--- a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -22,7 +22,7 @@ import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.sedona_sql.expressions.collect.ST_Collect
import org.apache.spark.sql.sedona_sql.expressions.raster._
import org.apache.spark.sql.sedona_sql.expressions._
-import
org.apache.spark.sql.sedona_sql.expressions.geography.{ST_GeogCollFromText,
ST_GeogFromEWKB, ST_GeogFromEWKT, ST_GeogFromGeoHash, ST_GeogFromText,
ST_GeogFromWKB, ST_GeogFromWKT, ST_GeogToGeometry}
+import
org.apache.spark.sql.sedona_sql.expressions.geography.{ST_GeogCollFromText,
ST_GeogFromEWKB, ST_GeogFromEWKT, ST_GeogFromGeoHash, ST_GeogFromText,
ST_GeogFromWKB, ST_GeogFromWKT, ST_GeogToGeometry, ST_GeomToGeography}
import org.locationtech.jts.geom.Geometry
import org.locationtech.jts.operation.buffer.BufferParameters
@@ -353,7 +353,8 @@ object Catalog extends AbstractCatalog {
function[ST_GLocal](),
function[ST_BinaryDistanceBandColumn](),
function[ST_WeightedDistanceBandColumn](),
- function[ST_GeogToGeometry]())
+ function[ST_GeogToGeometry](),
+ function[ST_GeomToGeography]())
val aggregateExpressions: Seq[Aggregator[Geometry, _, _]] =
Seq(new ST_Envelope_Aggr, new ST_Intersection_Aggr, new ST_Union_Aggr())
diff --git
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geography/Constructors.scala
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geography/Constructors.scala
index d010fa4b0f..ecb65a6210 100644
---
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geography/Constructors.scala
+++
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geography/Constructors.scala
@@ -24,6 +24,7 @@ import org.apache.spark.sql.catalyst.expressions.Expression
import org.apache.spark.sql.sedona_sql.UDT.GeographyUDT
import
org.apache.spark.sql.sedona_sql.expressions.InferrableFunctionConverter._
import org.apache.spark.sql.sedona_sql.expressions.{InferrableFunction,
InferredExpression}
+import org.locationtech.jts.geom.Geometry
/**
* Return a Geography from a WKT string
@@ -137,3 +138,17 @@ private[apache] case class
ST_GeogToGeometry(inputExpressions: Seq[Expression])
copy(inputExpressions = newChildren)
}
}
+
+/**
+ * Return a Geography from a Geometry
+ *
+ * @param inputExpressions
+ * This function takes a geometry object.
+ */
+private[apache] case class ST_GeomToGeography(inputExpressions:
Seq[Expression])
+ extends InferredExpression(Constructors.geomToGeography(_: Geometry)) {
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) =
{
+ copy(inputExpressions = newChildren)
+ }
+}
diff --git
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
index a811384993..22f2fa8699 100644
---
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
+++
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
@@ -20,7 +20,7 @@ package org.apache.spark.sql.sedona_sql.expressions
import org.apache.spark.sql.Column
import org.apache.spark.sql.sedona_sql.DataFrameShims.{wrapExpression, _}
-import
org.apache.spark.sql.sedona_sql.expressions.geography.{ST_GeogCollFromText,
ST_GeogFromEWKB, ST_GeogFromEWKT, ST_GeogFromGeoHash, ST_GeogFromText,
ST_GeogFromWKB, ST_GeogFromWKT, ST_GeogToGeometry}
+import
org.apache.spark.sql.sedona_sql.expressions.geography.{ST_GeogCollFromText,
ST_GeogFromEWKB, ST_GeogFromEWKT, ST_GeogFromGeoHash, ST_GeogFromText,
ST_GeogFromWKB, ST_GeogFromWKT, ST_GeogToGeometry, ST_GeomToGeography}
object st_constructors {
def ST_GeomFromGeoHash(geohash: Column, precision: Column): Column =
@@ -305,4 +305,7 @@ object st_constructors {
def ST_GeogToGeometry(geog: Column): Column =
wrapExpression[ST_GeogToGeometry](geog)
def ST_GeogToGeometry(geog: String): Column =
wrapExpression[ST_GeogToGeometry](geog)
+
+ def ST_GeomToGeography(geom: Column): Column =
wrapExpression[ST_GeomToGeography](geom)
+ def ST_GeomToGeography(geom: String): Column =
wrapExpression[ST_GeomToGeography](geom)
}
diff --git
a/spark/common/src/test/scala/org/apache/sedona/sql/geography/ConstructorsDataFrameAPITest.scala
b/spark/common/src/test/scala/org/apache/sedona/sql/geography/ConstructorsDataFrameAPITest.scala
index 610e3fba4d..39768f236f 100644
---
a/spark/common/src/test/scala/org/apache/sedona/sql/geography/ConstructorsDataFrameAPITest.scala
+++
b/spark/common/src/test/scala/org/apache/sedona/sql/geography/ConstructorsDataFrameAPITest.scala
@@ -127,4 +127,14 @@ class ConstructorsDataFrameAPITest extends TestBaseScala {
assert(geom.getSRID == 4326)
}
+ it("Passed ST_GeomToGeography multilinestring") {
+ var wkt = "MULTILINESTRING " + "((90 90, 20 20, 10 40), (40 40, 30 30, 40
20, 30 10))"
+ val df = sparkSession
+ .sql(s"SELECT '$wkt' AS wkt")
+ .select(st_constructors.ST_GeomFromWKT(col("wkt")).as("geom"))
+ .select(st_constructors.ST_GeomToGeography(col("geom")).as("geog"))
+ val geog = df.head().getAs[Geography]("geog")
+ assertEquals(wkt, geog.toString)
+ }
+
}
diff --git
a/spark/common/src/test/scala/org/apache/sedona/sql/geography/ConstructorsTest.scala
b/spark/common/src/test/scala/org/apache/sedona/sql/geography/ConstructorsTest.scala
index 4ffbe2d191..4606c6f8ec 100644
---
a/spark/common/src/test/scala/org/apache/sedona/sql/geography/ConstructorsTest.scala
+++
b/spark/common/src/test/scala/org/apache/sedona/sql/geography/ConstructorsTest.scala
@@ -233,4 +233,14 @@ class ConstructorsTest extends TestBaseScala {
assertEquals(4326, geom.getSRID)
assert(geom.getGeometryType == "LineString")
}
+
+ it("Passed ST_GeomToGeography multilinestring") {
+ var wkt = "MULTILINESTRING " + "((90 90, 20 20, 10 40), (40 40, 30 30, 40
20, 30 10))"
+ var df = sparkSession.sql(s"""
+ SELECT
+ ST_GeomToGeography(ST_GeomFromWKT('$wkt')) AS geog
+ """)
+ var geog = df.first().getAs[Geography](0)
+ assertEquals(wkt, geog.toString)
+ }
}