diff --git a/common/src/main/java/org/apache/sedona/common/geography/Functions.java b/common/src/main/java/org/apache/sedona/common/geography/Functions.java index e3166fa9e0f..d6a0b407b1e 100644 --- a/common/src/main/java/org/apache/sedona/common/geography/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/geography/Functions.java @@ -85,6 +85,12 @@ public static int nPoints(Geography g) { return toJTS(g).getNumPoints(); } + /** Return the WKT text representation of a geography. */ + public static String asText(Geography g) { + if (g == null) return null; + return toJTS(g).toText(); + } + // ─── Level 2: JTS + S2 geodesic metrics ────────────────────────────────── /** diff --git a/common/src/test/java/org/apache/sedona/common/Geography/FunctionTest.java b/common/src/test/java/org/apache/sedona/common/Geography/FunctionTest.java index 6787c5ad876..ab2daca5df5 100644 --- a/common/src/test/java/org/apache/sedona/common/Geography/FunctionTest.java +++ b/common/src/test/java/org/apache/sedona/common/Geography/FunctionTest.java @@ -29,7 +29,11 @@ import org.apache.sedona.common.geography.Constructors; import org.apache.sedona.common.geography.Functions; import org.junit.Test; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKTReader; public class FunctionTest { private static final double EPS = 1e-9; @@ -144,6 +148,37 @@ public void nPoints_polygon() throws ParseException { assertEquals(5, Functions.nPoints(g)); } + @Test + public void asText_point() throws ParseException { + Geography g = Constructors.geogFromWKT("POINT (1 2)", 4326); + String wkt = Functions.asText(g); + assertNotNull(wkt); + Point p = (Point) new WKTReader().read(wkt); + // S2 round-trip may introduce sub-nanometer floating-point drift; use a loose tolerance. + assertEquals(1.0, p.getX(), 1e-9); + assertEquals(2.0, p.getY(), 1e-9); + } + + @Test + public void asText_polygon() throws ParseException { + Geography g = Constructors.geogFromWKT("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", 4326); + String wkt = Functions.asText(g); + assertNotNull(wkt); + Polygon poly = (Polygon) new WKTReader().read(wkt); + Coordinate[] ring = poly.getExteriorRing().getCoordinates(); + assertEquals(5, ring.length); + double[][] expected = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}}; + for (int i = 0; i < expected.length; i++) { + assertEquals("ring[" + i + "].x", expected[i][0], ring[i].x, 1e-9); + assertEquals("ring[" + i + "].y", expected[i][1], ring[i].y, 1e-9); + } + } + + @Test + public void asText_nullHandling() { + assertNull(Functions.asText(null)); + } + // ─── Level 2: ST_Distance ──────────────────────────────────────────────── @Test diff --git a/docs/api/sql/geography/Geography-Functions.md b/docs/api/sql/geography/Geography-Functions.md index 7648b0c0a1e..86b5a12fb5f 100644 --- a/docs/api/sql/geography/Geography-Functions.md +++ b/docs/api/sql/geography/Geography-Functions.md @@ -42,6 +42,7 @@ These functions operate on geography type objects. | Function | Return type | Description | Since | | :--- | :--- | :--- | :--- | | [ST_AsEWKT](Geography-Functions/ST_AsEWKT.md) | String | Return the Extended Well-Known Text representation of a geography. | v1.8.0 | +| [ST_AsText](Geography-Functions/ST_AsText.md) | String | Return the Well-Known Text (WKT) representation of a geography. | v1.9.1 | | [ST_Envelope](Geography-Functions/ST_Envelope.md) | Geography | Return the bounding box (envelope) of a geography. Supports anti-meridian splitting. | v1.8.0 | | [ST_NPoints](Geography-Functions/ST_NPoints.md) | Integer | Return the number of points (vertices) in a geography. | v1.9.0 | | [ST_Distance](Geography-Functions/ST_Distance.md) | Double | Return the minimum geodesic distance between two geographies in meters. | v1.9.0 | diff --git a/docs/api/sql/geography/Geography-Functions/ST_AsText.md b/docs/api/sql/geography/Geography-Functions/ST_AsText.md new file mode 100644 index 00000000000..a00c06fc4d0 --- /dev/null +++ b/docs/api/sql/geography/Geography-Functions/ST_AsText.md @@ -0,0 +1,42 @@ + + +# ST_AsText + +Introduction: Returns the Well-Known Text (WKT) representation of a geography object. + +Format: + +`ST_AsText (A: Geography)` + +Return type: `String` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_AsText(ST_GeogFromWKT('POINT (1 2)')); +``` + +Output: + +``` +POINT (1 2) +``` diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala index 839ec2140f8..66f01a6ccf7 100644 --- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala +++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala @@ -571,7 +571,9 @@ private[apache] case class ST_SimplifyPolygonHull(inputExpressions: Seq[Expressi } private[apache] case class ST_AsText(inputExpressions: Seq[Expression]) - extends InferredExpression(Functions.asWKT _) { + extends InferredExpression( + inferrableFunction1(Functions.asWKT), + inferrableFunction1(org.apache.sedona.common.geography.Functions.asText)) { protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { copy(inputExpressions = newChildren) diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/geography/GeographyFunctionTest.scala b/spark/common/src/test/scala/org/apache/sedona/sql/geography/GeographyFunctionTest.scala index 7fe76730d27..7f47ae51826 100644 --- a/spark/common/src/test/scala/org/apache/sedona/sql/geography/GeographyFunctionTest.scala +++ b/spark/common/src/test/scala/org/apache/sedona/sql/geography/GeographyFunctionTest.scala @@ -23,7 +23,8 @@ import org.apache.sedona.sql.TestBaseScala import org.apache.spark.sql.functions.{col, lit} import org.apache.spark.sql.sedona_sql.expressions.{st_constructors, st_functions, st_predicates} import org.junit.Assert.{assertEquals, assertNotNull, assertTrue} -import org.locationtech.jts.geom.Geometry +import org.locationtech.jts.geom.{Geometry, Point} +import org.locationtech.jts.io.WKTReader /** * Spark SQL integration tests for Geography ST functions. Tests one representative function per @@ -77,7 +78,7 @@ class GeographyFunctionTest extends TestBaseScala { } } - // ─── Level 1: ST_NPoints ─────────────────────────────────────────────── + // ─── Level 1: ST_NPoints, ST_AsText ──────────────────────────────────── describe("Level 1: Structural") { @@ -87,6 +88,17 @@ class GeographyFunctionTest extends TestBaseScala { .first() assertEquals(3, row.getInt(0)) } + + it("ST_AsText") { + val row = sparkSession + .sql("SELECT ST_AsText(ST_GeogFromWKT('POINT (1 2)', 4326)) AS wkt") + .first() + val wkt = row.getString(0) + val point = new WKTReader().read(wkt).asInstanceOf[Point] + // S2 round-trip may introduce sub-nanometer floating-point drift; use a loose tolerance. + assertEquals(1.0, point.getX, 1e-9) + assertEquals(2.0, point.getY, 1e-9) + } } // ─── Level 2: ST_Distance ──────────────────────────────────────────────