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

Reply via email to