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 2023/08/16 08:19:13 UTC

[sedona] branch master updated: [SEDONA-363] Add RS_PixelAsPolygon (#972)

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 212fa8a3d [SEDONA-363] Add RS_PixelAsPolygon (#972)
212fa8a3d is described below

commit 212fa8a3dba4cf8a43607c2ad951e46e75eee60c
Author: Furqaanahmed Khan <46...@users.noreply.github.com>
AuthorDate: Wed Aug 16 04:19:07 2023 -0400

    [SEDONA-363] Add RS_PixelAsPolygon (#972)
---
 .../sedona/common/raster/PixelFunctions.java       | 21 +++++++++++++++++++++
 .../apache/sedona/common/raster/FunctionsTest.java | 15 +++++++++++++++
 docs/api/sql/Raster-operators.md                   | 22 ++++++++++++++++++++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../expressions/raster/PixelFunctions.scala        |  6 ++++++
 .../org/apache/sedona/sql/rasteralgebraTest.scala  | 12 ++++++++++++
 6 files changed, 77 insertions(+)

diff --git a/common/src/main/java/org/apache/sedona/common/raster/PixelFunctions.java b/common/src/main/java/org/apache/sedona/common/raster/PixelFunctions.java
index 13b27e06c..da78d4a54 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/PixelFunctions.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/PixelFunctions.java
@@ -41,6 +41,27 @@ public class PixelFunctions
         return values(rasterGeom, Collections.singletonList(geometry), band).get(0);
     }
 
+    public static Geometry getPixelAsPolygon(GridCoverage2D raster, int colX, int rowY) throws TransformException, FactoryException {
+        int srid = RasterAccessors.srid(raster);
+        Point2D point2D1 = RasterUtils.getWorldCornerCoordinates(raster, colX, rowY);
+        Point2D point2D2 = RasterUtils.getWorldCornerCoordinates(raster, colX + 1, rowY);
+        Point2D point2D3 = RasterUtils.getWorldCornerCoordinates(raster, colX + 1, rowY + 1);
+        Point2D point2D4 = RasterUtils.getWorldCornerCoordinates(raster, colX, rowY + 1);
+
+        Coordinate[] coordinateArray = new Coordinate[5];
+        coordinateArray[0] = new Coordinate(point2D1.getX(), point2D1.getY());
+        coordinateArray[1] = new Coordinate(point2D2.getX(), point2D2.getY());
+        coordinateArray[2] = new Coordinate(point2D3.getX(), point2D3.getY());
+        coordinateArray[3] = new Coordinate(point2D4.getX(), point2D4.getY());
+        coordinateArray[4] = new Coordinate(point2D1.getX(), point2D1.getY());
+
+        if(srid != 0) {
+            GeometryFactory factory = new GeometryFactory(new PrecisionModel(), srid);
+            return factory.createPolygon(coordinateArray);
+        }
+        return GEOMETRY_FACTORY.createPolygon(coordinateArray);
+    }
+
     public static Geometry getPixelAsPoint(GridCoverage2D raster, int colX, int rowY) throws TransformException, FactoryException {
         int srid = RasterAccessors.srid(raster);
         Point2D point2D = RasterUtils.getWorldCornerCoordinatesWithRangeCheck(raster, colX, rowY);
diff --git a/common/src/test/java/org/apache/sedona/common/raster/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/raster/FunctionsTest.java
index efdceeaf2..093b20185 100644
--- a/common/src/test/java/org/apache/sedona/common/raster/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/raster/FunctionsTest.java
@@ -13,6 +13,7 @@
  */
 package org.apache.sedona.common.raster;
 
+import org.apache.sedona.common.Functions;
 import org.apache.sedona.common.utils.RasterUtils;
 import org.geotools.coverage.grid.GridCoverage2D;
 import org.geotools.referencing.operation.transform.AffineTransform2D;
@@ -76,6 +77,20 @@ public class FunctionsTest extends RasterTestBase {
         assertEquals(255d, PixelFunctions.value(multiBandRaster, point(4.5d,4.5d), 4), 0.1d);
     }
 
+    @Test
+    public void testPixelAsPolygon() throws FactoryException, TransformException {
+        GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, 5, 10, 123, -230, 8);
+        String actual = Functions.asWKT(PixelFunctions.getPixelAsPolygon(emptyRaster, 2, 3));
+        String expected = "POLYGON ((131 -246, 139 -246, 139 -254, 131 -254, 131 -246))";
+        assertEquals(expected, actual);
+
+        // Testing with skewed rasters
+        emptyRaster = RasterConstructors.makeEmptyRaster(1, 5, 10, 234, -43, 3, 4, 2,3,0);
+        actual = Functions.asWKT(PixelFunctions.getPixelAsPolygon(emptyRaster, 2, 3));
+        expected = "POLYGON ((241 -32, 244 -29, 246 -25, 243 -28, 241 -32))";
+        assertEquals(expected,actual);
+    }
+
     @Test
     public void testPixelAsPointUpperLeft() throws FactoryException, TransformException {
         GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, 5, 10, 123, -230, 8);
diff --git a/docs/api/sql/Raster-operators.md b/docs/api/sql/Raster-operators.md
index 8cfd95d5c..bd03448fa 100644
--- a/docs/api/sql/Raster-operators.md
+++ b/docs/api/sql/Raster-operators.md
@@ -32,6 +32,28 @@ Output:
 IndexOutOfBoundsException: Specified pixel coordinates (6, 2) do not lie in the raster
 ```
 
+### RS_PixelAsPolygon
+
+Introduction: Returns a polygon geometry that bounds the specified pixel.
+The pixel coordinates specified are 1-indexed. 
+If `colX` and `rowY` are out of bounds for the raster, they are interpolated assuming the same skew and translate values.
+
+Format: `RS_PixelAsPolygon(raster: Raster, colX:int, rowY:int)`
+
+Since: `v1.5.0`
+
+Spark SQL Example:
+
+```sql
+SELECT ST_AsText(RS_PixelAsPolygon(RS_MakeEmptyRaster(1, 5, 10, 123, -230, 8), 2, 3)) FROM rasters
+```
+
+Output:
+
+```
+POLYGON ((131 -246, 139 -246, 139 -254, 131 -254, 131 -246))
+```
+
 ## Geometry Functions
 
 ### RS_Envelope
diff --git a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 5aedf8aef..d7233faeb 100644
--- a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -220,6 +220,7 @@ object Catalog {
     function[RS_SkewY](),
     function[RS_GeoReference](),
     function[RS_PixelAsPoint](),
+    function[RS_PixelAsPolygon](),
     function[RS_ConvexHull](),
     function[RS_RasterToWorldCoordX](),
     function[RS_RasterToWorldCoordY](),
diff --git a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/PixelFunctions.scala b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/PixelFunctions.scala
index ebf7aa6f1..461065781 100644
--- a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/PixelFunctions.scala
+++ b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/PixelFunctions.scala
@@ -42,6 +42,12 @@ case class RS_PixelAsPoint(inputExpressions: Seq[Expression]) extends InferredEx
   }
 }
 
+case class RS_PixelAsPolygon(inputExpressions: Seq[Expression]) extends InferredExpression(PixelFunctions.getPixelAsPolygon _) {
+  protected def withNewChildrenInternal(newChilren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChilren)
+  }
+}
+
 case class RS_Values(inputExpressions: Seq[Expression]) extends Expression with CodegenFallback with ExpectsInputTypes {
 
   override def nullable: Boolean = true
diff --git a/sql/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala b/sql/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
index 08d1af766..14678f41a 100644
--- a/sql/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
+++ b/sql/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
@@ -627,6 +627,18 @@ class rasteralgebraTest extends TestBaseScala with BeforeAndAfter with GivenWhen
       assertEquals(expectedY, actualCoordinates.y, 1e-5)
     }
 
+    it("Passed RS_PixelAsPolygon with raster") {
+      val widthInPixel = 5
+      val heightInPixel = 10
+      val upperLeftX = 123.19
+      val upperLeftY = -12
+      val cellSize = 4
+      val numBands = 2
+      val result = sparkSession.sql(s"SELECT ST_AsText(RS_PixelAsPolygon(RS_MakeEmptyRaster($numBands, $widthInPixel, $heightInPixel, $upperLeftX, $upperLeftY, $cellSize), 2, 3))").first().getString(0);
+      val expected = "POLYGON ((127.19000244140625 -20, 131.19000244140625 -20, 131.19000244140625 -24, 127.19000244140625 -24, 127.19000244140625 -20))"
+      assertEquals(expected, result)
+    }
+
     it("Passed RS_RasterToWorldCoordX with raster") {
       var df = sparkSession.read.format("binaryFile").load(resourceFolder + "raster/test1.tiff")
       df = df.selectExpr("RS_FromGeoTiff(content) as raster")