You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2022/10/10 05:33:42 UTC

[incubator-sedona] branch master updated: [SEDONA-125]Allows customized CRS in ST_Transform (#686)

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/incubator-sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new eb774081 [SEDONA-125]Allows customized CRS in ST_Transform (#686)
eb774081 is described below

commit eb774081da9673bc3777d38fe5bafa37105ec560
Author: Sachio Wakai <i....@gmail.com>
AuthorDate: Mon Oct 10 14:33:36 2022 +0900

    [SEDONA-125]Allows customized CRS in ST_Transform (#686)
    
    Co-authored-by: Adam Binford <ad...@gmail.com>
---
 .../java/org/apache/sedona/common/Functions.java   | 23 ++++++++-
 docs/api/flink/Function.md                         |  1 +
 docs/api/sql/Function.md                           |  3 +-
 .../apache/sedona/flink/expressions/Functions.java |  8 ++++
 .../java/org/apache/sedona/flink/FunctionTest.java | 36 ++++++++++++++
 .../sql/sedona_sql/expressions/Functions.scala     | 25 +++++-----
 .../org/apache/sedona/sql/functionTestScala.scala  | 56 +++++++++++++++++++++-
 7 files changed, 136 insertions(+), 16 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java b/common/src/main/java/org/apache/sedona/common/Functions.java
index aeb8abbc..be0eb151 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -34,6 +34,8 @@ import org.locationtech.jts.io.kml.KMLWriter;
 import org.locationtech.jts.operation.valid.IsSimpleOp;
 import org.locationtech.jts.operation.valid.IsValidOp;
 import org.opengis.referencing.FactoryException;
+
+import org.opengis.referencing.NoSuchAuthorityCodeException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
@@ -153,12 +155,29 @@ public class Functions {
 
     public static Geometry transform(Geometry geometry, String sourceCRS, String targetCRS, boolean lenient)
         throws FactoryException, TransformException {
-        CoordinateReferenceSystem sourceCRScode = CRS.decode(sourceCRS);
-        CoordinateReferenceSystem targetCRScode = CRS.decode(targetCRS);
+
+        CoordinateReferenceSystem sourceCRScode = parseCRSString(sourceCRS);
+        CoordinateReferenceSystem targetCRScode = parseCRSString(targetCRS);
         MathTransform transform = CRS.findMathTransform(sourceCRScode, targetCRScode, lenient);
         return JTS.transform(geometry, transform);
     }
 
+    private static CoordinateReferenceSystem parseCRSString(String CRSString) throws FactoryException {
+        try {
+            return CRS.parseWKT(CRSString);
+        }
+        catch (FactoryException ex1) {
+            try {
+                return CRS.decode(CRSString);
+            } catch (NoSuchAuthorityCodeException ex4) {
+                throw new NoSuchAuthorityCodeException("that Authority code cannot be found", CRSString, CRSString);
+            } catch (FactoryException ex6) {
+                throw new FactoryException("WKT format is illegal");
+            }
+        }
+
+    }
+
     public static Geometry flipCoordinates(Geometry geometry) {
         GeomUtils.flipCoordinates(geometry);
         return geometry;
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 308a0556..e7a63000 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -693,6 +693,7 @@ FROM polygondf
 Introduction:
 
 Transform the Spatial Reference System / Coordinate Reference System of A, from SourceCRS to TargetCRS
+For SourceCRS and TargetCRS, WKT format is also available since v1.3.1.
 
 !!!note
 By default, this function uses lat/lon order. You can use ==ST_FlipCoordinates== to swap X and Y.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 6a55b969..8453058c 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -1284,7 +1284,8 @@ MULTIPOLYGON (((-2 -3, -3 -3, -3 3, -2 3, -2 -3)), ((3 -3, 3 3, 4 3, 4 -3, 3 -3)
 
 Introduction:
 
-Transform the Spatial Reference System / Coordinate Reference System of A, from SourceCRS to TargetCRS
+Transform the Spatial Reference System / Coordinate Reference System of A, from SourceCRS to TargetCRS.
+For SourceCRS and TargetCRS, WKT format is also available since v1.3.1.
 
 !!!note
 	By default, this function uses lat/lon order. You can use ==ST_FlipCoordinates== to swap X and Y.
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 1232f3cc..e92a1d6d 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
@@ -115,6 +115,14 @@ public class Functions {
             Geometry geom = (Geometry) o;
             return org.apache.sedona.common.Functions.transform(geom, sourceCRS, targetCRS);
         }
+
+        @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
+        public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o, @DataTypeHint("String") String sourceCRS, @DataTypeHint("String") String targetCRS, @DataTypeHint("Boolean") Boolean lenient)
+                throws FactoryException, TransformException {
+            Geometry geom = (Geometry) o;
+            return org.apache.sedona.common.Functions.transform(geom, sourceCRS, targetCRS, lenient);
+        }
+
     }
 
     public static class ST_FlipCoordinates extends ScalarFunction {
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 1afeb47d..4a21efa3 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -16,6 +16,7 @@ package org.apache.sedona.flink;
 import org.apache.commons.codec.binary.Hex;
 import org.apache.flink.table.api.Table;
 import org.apache.sedona.flink.expressions.Functions;
+import org.geotools.referencing.CRS;
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -23,6 +24,8 @@ import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.LinearRing;
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.Polygon;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 import static org.apache.flink.table.api.Expressions.$;
 import static org.apache.flink.table.api.Expressions.call;
@@ -92,6 +95,39 @@ public class FunctionTest extends TestBase{
         assertEquals("POINT (-13134586.718698347 3764623.3541299687)", result);
     }
 
+    @Test
+    public void testTransformWKT() throws FactoryException {
+        Table pointTable = createPointTable_real(testDataSize);
+
+        CoordinateReferenceSystem CRS_SRC = CRS.decode("epsg:4326");
+        CoordinateReferenceSystem CRS_TGT = CRS.decode("epsg:3857");
+
+        String SRC_WKT = CRS_SRC.toWKT();
+        String TGT_WKT = CRS_TGT.toWKT();
+
+        Table transformedTable_SRC = pointTable.select(call(Functions.ST_Transform.class.getSimpleName(), $(pointColNames[0])
+                , SRC_WKT, "epsg:3857"));
+        String result_SRC = first(transformedTable_SRC).getField(0).toString();
+        assertEquals("POINT (-13134586.718698347 3764623.3541299687)", result_SRC);
+
+        Table transformedTable_TGT = pointTable.select(call(Functions.ST_Transform.class.getSimpleName(), $(pointColNames[0])
+                , "epsg:4326", TGT_WKT));
+        String result_TGT = first(transformedTable_TGT).getField(0).toString();
+        assertEquals("POINT (-13134586.718698347 3764623.3541299687)", result_TGT);
+
+        Table transformedTable_SRC_TGT = pointTable.select(call(Functions.ST_Transform.class.getSimpleName(), $(pointColNames[0])
+                , SRC_WKT, TGT_WKT));
+        String result_SRC_TGT = first(transformedTable_SRC_TGT).getField(0).toString();
+        assertEquals("POINT (-13134586.718698347 3764623.3541299687)", result_SRC_TGT);
+
+        Table transformedTable_SRC_TGT_lenient = pointTable.select(call(Functions.ST_Transform.class.getSimpleName(), $(pointColNames[0])
+                , SRC_WKT, TGT_WKT,false));
+        String result_SRC_TGT_lenient = first(transformedTable_SRC_TGT_lenient).getField(0).toString();
+        assertEquals("POINT (-13134586.718698347 3764623.3541299687)", result_SRC_TGT_lenient);
+
+    }
+
+
     @Test
     public void testDistance() {
         Table pointTable = createPointTable(testDataSize);
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 065539e0..1ae659ca 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -31,8 +31,8 @@ import org.apache.spark.sql.sedona_sql.expressions.subdivide.GeometrySubDivider
 import org.apache.spark.sql.types._
 import org.apache.spark.unsafe.types.UTF8String
 import org.locationtech.jts.algorithm.MinimumBoundingCircle
+import org.locationtech.jts.geom.{Geometry, _}
 import org.locationtech.jts.geom.util.GeometryFixer
-import org.locationtech.jts.geom._
 import org.locationtech.jts.linearref.LengthIndexedLine
 import org.locationtech.jts.operation.buffer.BufferParameters
 import org.locationtech.jts.operation.linemerge.LineMerger
@@ -200,19 +200,20 @@ case class ST_Transform(inputExpressions: Seq[Expression])
 
   override def eval(input: InternalRow): Any = {
     val geometry = inputExpressions(0).toGeometry(input)
-    val sourceCRS = inputExpressions(1).asString(input)
-    val targetCRS = inputExpressions(2).asString(input)
+    val sourceCRSString = inputExpressions(1).asString(input)
+    val targetCRSString = inputExpressions(2).asString(input)
 
-    (geometry, sourceCRS, targetCRS) match {
-      case (geometry: Geometry, sourceCRS: String, targetCRS: String) =>
-        val lenient = if (inputExpressions.length == 4) {
-          inputExpressions(3).eval(input).asInstanceOf[Boolean]
-        } else {
-          false
-        }
-        Functions.transform(geometry, sourceCRS, targetCRS, lenient).toGenericArrayData
-      case (_, _, _) => null
+    val lenient = if (inputExpressions.length == 4) {
+      inputExpressions(3).eval(input).asInstanceOf[Boolean]
+    } else {
+      false
     }
+
+    (geometry,sourceCRSString,targetCRSString,lenient) match {
+      case (null,_,_,_)  => null
+      case _ => Functions.transform (geometry, sourceCRSString, targetCRSString, lenient).toGenericArrayData
+    }
+
   }
 
   override def dataType: DataType = GeometryUDT
diff --git a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index f642f34c..ccbb30f7 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -30,10 +30,14 @@ import org.locationtech.jts.linearref.LengthIndexedLine
 import org.locationtech.jts.operation.distance3d.Distance3DOp
 import org.scalatest.{GivenWhenThen, Matchers}
 import org.xml.sax.InputSource
-
 import java.io.StringReader
+
 import javax.xml.parsers.DocumentBuilderFactory
 import javax.xml.xpath.XPathFactory
+import org.apache.sedona.sql.utils.GeometrySerializer
+import org.apache.spark.SparkException
+import org.geotools.referencing.CRS
+import org.opengis.referencing.{FactoryException, NoSuchAuthorityCodeException}
 
 class functionTestScala extends TestBaseScala with Matchers with GeometrySample with GivenWhenThen {
 
@@ -140,6 +144,56 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
       assert(forceXYResult == forceXYExpect)
     }
 
+    it("Passed ST_transform WKT version"){
+      var polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation)
+      polygonWktDf.createOrReplaceTempView("polygontable")
+      var polygonDf = sparkSession.sql("select ST_GeomFromWKT(polygontable._c0) as countyshape from polygontable")
+      polygonDf.createOrReplaceTempView("polygondf")
+      val polygon = "POLYGON ((110.54671 55.818002, 110.54671 55.143743, 110.940494 55.143743, 110.940494 55.818002, 110.54671 55.818002))"
+      val forceXYExpect = "POLYGON ((471596.69167460164 6185916.951191288, 471107.5623640998 6110880.974228167, 496207.109151055 6110788.804712435, 496271.31937046186 6185825.60569904, 471596.69167460164 6185916.951191288))"
+
+      val EPSG_TGT_CRS = CRS.decode("EPSG:32649")
+      val EPSG_TGT_WKT = EPSG_TGT_CRS.toWKT()
+      val EPSG_SRC_CRS = CRS.decode("EPSG:4326")
+      val EPSG_SRC_WKT = EPSG_SRC_CRS.toWKT()
+
+      sparkSession.createDataset(Seq(polygon))
+        .withColumn("geom", expr("ST_GeomFromWKT(value)"))
+        .createOrReplaceTempView("df")
+
+      val forceXYResult_TGT_WKT = sparkSession.sql(s"""select ST_Transform(ST_FlipCoordinates(ST_geomFromWKT('$polygon')),'EPSG:4326', '$EPSG_TGT_WKT', false)""").rdd.map(row => row.getAs[Geometry](0).toString).collect()(0)
+      assert(forceXYResult_TGT_WKT == forceXYExpect)
+
+      val forceXYResult_SRC_WKT = sparkSession.sql(s"""select ST_Transform(ST_FlipCoordinates(ST_geomFromWKT('$polygon')),'$EPSG_SRC_WKT', 'EPSG:32649', false)""").rdd.map(row => row.getAs[Geometry](0).toString).collect()(0)
+      assert(forceXYResult_SRC_WKT == forceXYExpect)
+
+      val forceXYResult_SRC_TGT_WKT = sparkSession.sql(s"""select ST_Transform(ST_FlipCoordinates(ST_geomFromWKT('$polygon')),'$EPSG_SRC_WKT', '$EPSG_TGT_WKT', false)""").rdd.map(row => row.getAs[Geometry](0).toString).collect()(0)
+      assert(forceXYResult_SRC_TGT_WKT == forceXYExpect)
+
+    }
+
+    it("Passed Function exception check"){
+      val EPSG_TGT_CRS = CRS.decode("EPSG:32649")
+      val EPSG_TGT_WKT = EPSG_TGT_CRS.toWKT()
+      val epsgFactoryErrorString = EPSG_TGT_WKT.substring(0,EPSG_TGT_WKT.length() - 1)
+      val epsgString = "EPSG:4326"
+      val epsgNoSuchAuthorityString = "EPSG:9377"
+      val polygon = "POLYGON ((110.54671 55.818002, 110.54671 55.143743, 110.940494 55.143743, 110.940494 55.818002, 110.54671 55.818002))"
+      import org.locationtech.jts.io.WKTReader
+      val reader = new WKTReader
+      val polygeom = reader.read(polygon)
+
+      intercept[FactoryException]{
+        val d = org.apache.sedona.common.Functions.transform(polygeom, epsgString, epsgFactoryErrorString)
+      }
+
+      intercept[NoSuchAuthorityCodeException]{
+        val d2 = org.apache.sedona.common.Functions.transform(polygeom, epsgString, epsgNoSuchAuthorityString)
+      }
+
+      }
+
+
     it("Passed ST_Intersection - intersects but not contains") {
 
       val testtable = sparkSession.sql("select ST_GeomFromWKT('POLYGON((1 1, 8 1, 8 8, 1 8, 1 1))') as a,ST_GeomFromWKT('POLYGON((2 2, 9 2, 9 9, 2 9, 2 2))') as b")