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 5bd0172a8 [SEDONA-411] Add RS_Rotation (#1061)
5bd0172a8 is described below

commit 5bd0172a8865b162d93320d1229ec59f57a79088
Author: Furqaan Khan <[email protected]>
AuthorDate: Wed Nov 1 01:22:29 2023 +0530

    [SEDONA-411] Add RS_Rotation (#1061)
---
 .../sedona/common/raster/RasterAccessors.java      | 64 ++++++++++++++++++++++
 .../sedona/common/raster/RasterAccessorsTest.java  | 19 ++++++-
 docs/api/sql/Raster-operators.md                   | 54 ++++++++++++++++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  2 +
 .../expressions/raster/RasterAccessors.scala       | 12 ++++
 .../org/apache/sedona/sql/rasteralgebraTest.scala  | 14 +++++
 6 files changed, 163 insertions(+), 2 deletions(-)

diff --git 
a/common/src/main/java/org/apache/sedona/common/raster/RasterAccessors.java 
b/common/src/main/java/org/apache/sedona/common/raster/RasterAccessors.java
index 5d1d82cc2..20062d425 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/RasterAccessors.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/RasterAccessors.java
@@ -32,6 +32,7 @@ import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
 
+import java.awt.image.RenderedImage;
 import java.util.Arrays;
 import java.util.Set;
 
@@ -127,6 +128,69 @@ public class RasterAccessors
         }
     }
 
+
+    public static double[] getGeoTransform(GridCoverage2D raster) throws 
FactoryException {
+        // size of a pixel along the transformed i axis
+        double magnitudeI;
+
+        // size of a pixel along the transformed j axis
+        double magnitudeJ;
+
+        // angle by which the raster is rotated (Radians positive clockwise)
+        double thetaI;
+
+        // angle from transformed i axis to transformed j axis (Radians 
positive counter-clockwise)
+        double thetaIJ;
+
+        double[] metadata = metadata(raster);
+
+        // x ordinate of the upper-left corner of the upper-left pixel
+        double offsetX = metadata[0];
+
+        // y ordinate of the upper-left corner of the upper-left pixel
+        double offsetY = metadata[1];
+
+        double scaleX = metadata[4];
+        double scaleY =  metadata[5];
+        double skewX = metadata[6];
+        double skewY = metadata[7];
+
+        // pixel size in i direction - west-east
+        magnitudeI = Math.sqrt(scaleX * scaleX + skewY * skewY);
+
+        // pixel size in j direction - north-south
+        magnitudeJ = Math.sqrt(scaleY * scaleY + skewX * skewX);
+
+        // Rotation
+        thetaI = Math.acos(scaleX / magnitudeI);
+        double thetaTest = Math.acos(skewY / magnitudeI);
+        if (thetaTest < Math.PI / 2) {
+            thetaI = -thetaI;
+        }
+
+        // Angular separation
+        thetaIJ = Math.acos((((scaleX * skewX) + (skewY * scaleY)) / 
(magnitudeI * magnitudeJ)));
+        thetaTest = Math.acos(((-skewY * skewX) + (scaleX * scaleY)) / 
(magnitudeI * magnitudeJ));
+        if (thetaTest > Math.PI / 2) {
+            thetaIJ = -thetaIJ;
+        }
+
+        double[] result = new double[6];
+        result[0] = magnitudeI;
+        result[1] = magnitudeJ;
+        result[2] = thetaI;
+        result[3] = thetaIJ;
+        result[4] = offsetX;
+        result[5] = offsetY;
+
+        return result;
+    }
+
+    public static double getRotation(GridCoverage2D raster) throws 
FactoryException {
+        double rotation = getGeoTransform(raster)[2];
+        return rotation;
+    }
+
     public static Geometry getGridCoord(GridCoverage2D raster, double x, 
double y) throws TransformException {
         int[] coords = RasterUtils.getGridCoordinatesFromWorld(raster, x, y);
         coords = Arrays.stream(coords).map(number -> number + 1).toArray();
diff --git 
a/common/src/test/java/org/apache/sedona/common/raster/RasterAccessorsTest.java 
b/common/src/test/java/org/apache/sedona/common/raster/RasterAccessorsTest.java
index 541b7fa81..2193225f0 100644
--- 
a/common/src/test/java/org/apache/sedona/common/raster/RasterAccessorsTest.java
+++ 
b/common/src/test/java/org/apache/sedona/common/raster/RasterAccessorsTest.java
@@ -29,8 +29,7 @@ import org.opengis.referencing.operation.TransformException;
 
 import java.io.IOException;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.*;
 
 public class RasterAccessorsTest extends RasterTestBase
 {
@@ -60,6 +59,22 @@ public class RasterAccessorsTest extends RasterTestBase
         assertEquals(4326, RasterAccessors.srid(multiBandRaster));
     }
 
+    @Test
+    public void testRotation() throws IOException, FactoryException {
+        GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(2, 10, 
15, 1, 2, 1, -2, 10, 10, 0);
+        double actual = RasterAccessors.getRotation(emptyRaster);
+        double expected = -1.4711276743037347;
+        assertEquals(expected, actual, 1e-9);
+    }
+
+    @Test
+    public void testGeoTransform() throws FactoryException {
+        GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, 10, 
15, 1, 2, 1, -1, 10, 10, 0);
+        double[] actual = RasterAccessors.getGeoTransform(emptyRaster);
+        double[] expected = new double[] {10.04987562112089, 
10.04987562112089, -1.4711276743037347, -1.5707963267948966, 1.0, 2.0};
+        assertArrayEquals(expected, actual, 1e-9);
+    }
+
     @Test
     public void testUpperLeftX() throws FactoryException {
         GridCoverage2D gridCoverage2D = RasterConstructors.makeEmptyRaster(1, 
3, 4, 1,2, 5);
diff --git a/docs/api/sql/Raster-operators.md b/docs/api/sql/Raster-operators.md
index 4bda00305..3bafeb448 100644
--- a/docs/api/sql/Raster-operators.md
+++ b/docs/api/sql/Raster-operators.md
@@ -292,6 +292,38 @@ SELECT RS_GeoReferrence(ST_MakeEmptyRaster(1, 3, 4, 100.0, 
200.0,2.0, -3.0, 0.1,
 198.500000
 ```
 
+### RS_GeoTransform
+
+Introduction: Returns an array of parameters that represent the 
GeoTranformation of the raster. The array contains the following values:
+
+- 0: pixel width along west-east axis (x axis)
+- 1: pixel height along north-south axis (y axis)
+- 2: Rotation of the raster
+- 3: Angular separation between x axis and y axis
+- 4: X ordinate of upper-left coordinate
+- 5: Y ordinate of upper-left coordinate
+
+!!!note
+    Refer to [this 
image](https://www.researchgate.net/figure/Relation-between-the-cartesian-axes-x-y-and-i-j-axes-of-the-pixels_fig3_313860913)
 for a clear understanding between i & j axis and x & y axis.
+
+Format: `RS_GeoTransform(raster: Raster)`
+
+Since: `v1.5.1`
+
+Spark SQL Example:
+
+```sql
+SELECT RS_GeoTransform(
+        RS_MakeEmptyRaster(2, 10, 15, 1, 2, 1, -2, 1, 2, 0)
+       )
+```
+
+Output:
+
+```
+[2.23606797749979, 2.23606797749979, -1.1071487177940904, -2.214297435588181, 
1.0, 2.0]
+```
+
 ### RS_Height
 
 Introduction: Returns the height of the raster.
@@ -352,6 +384,28 @@ Output:
 54
 ```
 
+### RS_Rotation
+
+Introduction: Returns the uniform rotation of the raster in radian.
+
+Format: `RS_Rotation(raster: Raster)`
+
+Since: `v1.5.1`
+
+Spark SQL Example:
+
+```sql
+SELECT RS_Rotation(
+        RS_MakeEmptyRaster(2, 10, 15, 1, 2, 1, -2, 1, 2, 0)
+        )
+```
+
+Output:
+
+```
+-1.1071487177940904
+```
+
 ### RS_ScaleX
 
 Introduction: Returns the pixel width of the raster in CRS units.
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 98e258584..92238b742 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
@@ -226,6 +226,8 @@ object Catalog {
     function[RS_SkewX](),
     function[RS_SkewY](),
     function[RS_GeoReference](),
+    function[RS_Rotation](),
+    function[RS_GeoTransform](),
     function[RS_PixelAsPoint](),
     function[RS_PixelAsPolygon](),
     function[RS_PixelAsCentroid](),
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
index 77aa82ecc..e6f13eb3f 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
@@ -86,6 +86,18 @@ case class RS_GeoReference(inputExpressions: 
Seq[Expression]) extends InferredEx
   }
 }
 
+case class RS_Rotation(inputExpressions: Seq[Expression]) extends 
InferredExpression(RasterAccessors.getRotation _) {
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
+    copy(inputExpressions = newChildren)
+  }
+}
+
+case class RS_GeoTransform(inputExpressions: Seq[Expression]) extends 
InferredExpression(RasterAccessors.getGeoTransform _) {
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
+    copy(inputExpressions = newChildren)
+  }
+}
+
 case class RS_SkewX(inputExpressions: Seq[Expression]) extends 
InferredExpression(RasterAccessors.getSkewX _) {
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): 
Expression = {
     copy(inputExpressions = newChildren)
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
index e533b8fda..ba4767c4c 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
@@ -609,6 +609,20 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       assertEquals(expected, result)
     }
 
+    it("Passed RS_Rotation") {
+      val df = sparkSession.sql("SELECT RS_MakeEmptyRaster(2, 10, 15, 1, 2, 1, 
-2, 1, 2, 0) as raster")
+      val actual = df.selectExpr("RS_Rotation(raster)").first().get(0)
+      val expected = -1.1071487177940904
+      assertEquals(expected, actual)
+    }
+
+    it("Passed RS_GeoTransform") {
+      val df = sparkSession.sql("SELECT RS_MakeEmptyRaster(2, 10, 15, 1, 2, 1, 
-2, 1, 2, 0) as raster")
+      val actual = df.selectExpr("RS_GeoTransform(raster)").first().getSeq(0)
+      val expected = mutable.ArraySeq(2.23606797749979, 2.23606797749979, 
-1.1071487177940904, -2.214297435588181, 1.0, 2.0)
+      assertTrue(expected.equals(actual))
+    }
+
     it("Passed RS_SkewX") {
       val df = sparkSession.read.format("binaryFile").load(resourceFolder + 
"raster/test1.tiff")
       val result = 
df.selectExpr("RS_SkewX(RS_FromGeoTiff(content))").first().getDouble(0)

Reply via email to