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")