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 6eedd19f [SEDONA-340] Add RS_ConvexHull | [SEDONA-341] Move 
RS_Envelope to Geometry Functions (#942)
6eedd19f is described below

commit 6eedd19fb01412c18d9cdb1fb6b96b13710e53ef
Author: Nilesh Gajwani <[email protected]>
AuthorDate: Wed Aug 2 17:53:51 2023 -0700

    [SEDONA-340] Add RS_ConvexHull | [SEDONA-341] Move RS_Envelope to Geometry 
Functions (#942)
---
 .../sedona/common/raster/GeometryFunctions.java    |  71 +++++++++++++
 .../sedona/common/raster/RasterAccessors.java      |  13 ---
 .../apache/sedona/common/utils/RasterUtils.java    |   8 ++
 .../apache/sedona/common/raster/FunctionsTest.java |   4 +-
 .../common/raster/GeometryFunctionsTest.java       | 112 +++++++++++++++++++++
 .../sedona/common/raster/RasterAccessorsTest.java  |  34 -------
 .../common/raster/RasterConstructorsTest.java      |   8 +-
 docs/api/sql/Raster-operators.md                   |  55 ++++++----
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |   3 +-
 .../expressions/raster/GeometryFunctions.scala     |  37 +++++++
 .../expressions/raster/RasterAccessors.scala       |   8 +-
 .../org/apache/sedona/sql/rasteralgebraTest.scala  |  29 +++++-
 12 files changed, 303 insertions(+), 79 deletions(-)

diff --git 
a/common/src/main/java/org/apache/sedona/common/raster/GeometryFunctions.java 
b/common/src/main/java/org/apache/sedona/common/raster/GeometryFunctions.java
new file mode 100644
index 00000000..6e7f2817
--- /dev/null
+++ 
b/common/src/main/java/org/apache/sedona/common/raster/GeometryFunctions.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sedona.common.raster;
+
+import org.apache.sedona.common.utils.RasterUtils;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.geotools.geometry.Envelope2D;
+import org.locationtech.jts.geom.*;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+
+import java.awt.geom.Point2D;
+
+public class GeometryFunctions {
+
+    /***
+     * Returns the convex hull of the input raster
+     * @param raster
+     * @return Geometry: convex hull of the input raster
+     * @throws FactoryException
+     * @throws TransformException
+     */
+    public static Geometry convexHull(GridCoverage2D raster) throws 
FactoryException, TransformException {
+        int width = RasterAccessors.getWidth(raster), height = 
RasterAccessors.getHeight(raster);
+        GeometryFactory geometryFactory = new GeometryFactory(new 
PrecisionModel(), RasterAccessors.srid(raster));
+        double upperLeftX = RasterAccessors.getUpperLeftX(raster), upperLeftY 
= RasterAccessors.getUpperLeftY(raster);
+        //First and last coord (Upper left)
+        Coordinate coordOne = new Coordinate(upperLeftX, upperLeftY);
+
+        //start clockwise rotation
+
+        //upper right
+        Point2D point = RasterUtils.getCornerCoordinates(raster, width + 1, 1);
+        Coordinate coordTwo = new Coordinate(point.getX(), point.getY());
+
+        //lower right
+        point = RasterUtils.getCornerCoordinates(raster, width + 1, height + 
1);
+        Coordinate coordThree = new Coordinate(point.getX(), point.getY());
+
+        //lower left
+        point = RasterUtils.getCornerCoordinates(raster, 1, height + 1);
+        Coordinate coordFour = new Coordinate(point.getX(), point.getY());
+
+        return geometryFactory.createPolygon(new Coordinate[] {coordOne, 
coordTwo, coordThree, coordFour, coordOne});
+    }
+
+    public static Geometry envelope(GridCoverage2D raster) throws 
FactoryException {
+        Envelope2D envelope2D = raster.getEnvelope2D();
+
+        Envelope envelope = new Envelope(envelope2D.getMinX(), 
envelope2D.getMaxX(), envelope2D.getMinY(), envelope2D.getMaxY());
+        int srid = RasterAccessors.srid(raster);
+        return new GeometryFactory(new PrecisionModel(), 
srid).toGeometry(envelope);
+    }
+}
\ No newline at end of file
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 07aaab24..3ab1d27f 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
@@ -21,14 +21,9 @@ package org.apache.sedona.common.raster;
 import org.apache.sedona.common.utils.RasterUtils;
 import org.geotools.coverage.grid.GridCoverage2D;
 import org.geotools.coverage.grid.GridEnvelope2D;
-import org.geotools.geometry.Envelope2D;
 import org.geotools.referencing.CRS;
 import org.geotools.referencing.crs.DefaultEngineeringCRS;
 import org.geotools.referencing.operation.transform.AffineTransform2D;
-import org.locationtech.jts.geom.Envelope;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.PrecisionModel;
 import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
@@ -79,14 +74,6 @@ public class RasterAccessors
         return RasterUtils.getGDALAffineTransform(raster).getScaleY();
     }
 
-    public static Geometry envelope(GridCoverage2D raster) throws 
FactoryException {
-        Envelope2D envelope2D = raster.getEnvelope2D();
-
-        Envelope envelope = new Envelope(envelope2D.getMinX(), 
envelope2D.getMaxX(), envelope2D.getMinY(), envelope2D.getMaxY());
-        int srid = srid(raster);
-        return new GeometryFactory(new PrecisionModel(), 
srid).toGeometry(envelope);
-    }
-
     /**
      * Returns the metadata of a raster as an array of doubles.
      * @param raster the raster
diff --git 
a/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java 
b/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
index a120c88b..b60b3cd5 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
@@ -21,15 +21,19 @@ package org.apache.sedona.common.utils;
 import com.sun.media.imageioimpl.common.BogusColorSpace;
 import org.geotools.coverage.CoverageFactoryFinder;
 import org.geotools.coverage.GridSampleDimension;
+import org.geotools.coverage.grid.GridCoordinates2D;
 import org.geotools.coverage.grid.GridCoverage2D;
 import org.geotools.coverage.grid.GridCoverageFactory;
 import org.geotools.coverage.grid.GridGeometry2D;
+import org.geotools.geometry.DirectPosition2D;
 import org.geotools.referencing.operation.transform.AffineTransform2D;
 import org.opengis.metadata.spatial.PixelOrientation;
 import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
 
 import java.awt.Transparency;
 import java.awt.color.ColorSpace;
+import java.awt.geom.Point2D;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
 import java.awt.image.ComponentColorModel;
@@ -88,4 +92,8 @@ public class RasterUtils {
         }
         return (AffineTransform2D) crsTransform;
     }
+
+    public static Point2D getCornerCoordinates(GridCoverage2D raster, int 
colX, int rowY) throws TransformException {
+        return 
raster.getGridGeometry().getGridToCRS2D(PixelOrientation.UPPER_LEFT).transform(new
 GridCoordinates2D(colX - 1, rowY - 1), null);
+    }
 }
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 d98441f5..9a1f2033 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
@@ -38,8 +38,8 @@ public class FunctionsTest extends RasterTestBase {
 
         GridCoverage2D oneBandRasterWithUpdatedSrid = 
RasterEditors.setSrid(oneBandRaster, 4326);
         assertEquals(4326, RasterAccessors.srid(oneBandRasterWithUpdatedSrid));
-        assertEquals(4326, 
RasterAccessors.envelope(oneBandRasterWithUpdatedSrid).getSRID());
-        
assertTrue(RasterAccessors.envelope(oneBandRasterWithUpdatedSrid).equalsTopo(RasterAccessors.envelope(oneBandRaster)));
+        assertEquals(4326, 
GeometryFunctions.envelope(oneBandRasterWithUpdatedSrid).getSRID());
+        
assertTrue(GeometryFunctions.envelope(oneBandRasterWithUpdatedSrid).equalsTopo(GeometryFunctions.envelope(oneBandRaster)));
 
         GridCoverage2D multiBandRasterWithUpdatedSrid = 
RasterEditors.setSrid(multiBandRaster, 0);
         assertEquals(0 , RasterAccessors.srid(multiBandRasterWithUpdatedSrid));
diff --git 
a/common/src/test/java/org/apache/sedona/common/raster/GeometryFunctionsTest.java
 
b/common/src/test/java/org/apache/sedona/common/raster/GeometryFunctionsTest.java
new file mode 100644
index 00000000..56f37ac9
--- /dev/null
+++ 
b/common/src/test/java/org/apache/sedona/common/raster/GeometryFunctionsTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sedona.common.raster;
+
+import org.apache.sedona.common.Functions;
+import org.geotools.coverage.grid.GridCoverage2D;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+
+import java.io.IOException;
+
+public class GeometryFunctionsTest extends RasterTestBase {
+
+    @Test
+    public void testConvexHull() throws FactoryException, TransformException {
+        GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, 5, 
10, 156, -132, 5);
+        Geometry convexHull = GeometryFunctions.convexHull(emptyRaster);
+        String expected = "POLYGON ((156 -132, 181 -132, 181 -182, 156 -182, 
156 -132))";
+        String actual = Functions.asWKT(convexHull);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testConvexHullSkewed() throws FactoryException, 
TransformException {
+        GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, 5, 
10, 156, -132, 5, 10, 3, 5, 0);
+        Geometry convexHull = GeometryFunctions.convexHull(emptyRaster);
+        String expected = "POLYGON ((156 -132, 181 -107, 211 -7, 186 -32, 156 
-132))";
+        String actual = Functions.asWKT(convexHull);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testConvexHullFromRasterFile() throws FactoryException, 
TransformException, IOException {
+        GridCoverage2D raster = rasterFromGeoTiff(resourceFolder + 
"raster/test1.tiff");
+        Geometry convexHull = GeometryFunctions.convexHull(raster);
+        Coordinate[] coordinates = convexHull.getCoordinates();
+        Coordinate expectedCoordOne = new Coordinate(-13095817.809482181, 
4021262.7487925636);
+        Coordinate expectedCoordTwo = new Coordinate(-13058785.559768861, 
4021262.7487925636);
+        Coordinate expectedCoordThree = new Coordinate(-13058785.559768861, 
3983868.8560156375);
+        Coordinate expectedCoordFour = new Coordinate(-13095817.809482181, 
3983868.8560156375);
+        assertEquals(expectedCoordOne.getX(), coordinates[0].getX(), 0.5d);
+        assertEquals(expectedCoordOne.getY(), coordinates[0].getY(), 0.5d);
+
+        assertEquals(expectedCoordTwo.getX(), coordinates[1].getX(), 0.5d);
+        assertEquals(expectedCoordTwo.getY(), coordinates[1].getY(), 0.5d);
+
+        assertEquals(expectedCoordThree.getX(), coordinates[2].getX(), 0.5d);
+        assertEquals(expectedCoordThree.getY(), coordinates[2].getY(), 0.5d);
+
+        assertEquals(expectedCoordFour.getX(), coordinates[3].getX(), 0.5d);
+        assertEquals(expectedCoordFour.getY(), coordinates[3].getY(), 0.5d);
+
+        assertEquals(expectedCoordOne.getX(), coordinates[4].getX(), 0.5d);
+        assertEquals(expectedCoordOne.getY(), coordinates[4].getY(), 0.5d);
+    }
+
+    @Test
+    public void envelope() throws FactoryException
+    {
+        Geometry envelope = GeometryFunctions.envelope(oneBandRaster);
+        assertEquals(3600.0d, envelope.getArea(), 0.1d);
+        assertEquals(378922.0d + 30.0d, envelope.getCentroid().getX(), 0.1d);
+        assertEquals(4072345.0d + 30.0d, envelope.getCentroid().getY(), 0.1d);
+        assertEquals(4326, 
GeometryFunctions.envelope(multiBandRaster).getSRID());
+    }
+
+    @Test
+    public void testEnvelopeUsingSkewedRaster() throws FactoryException {
+        GridCoverage2D raster = RasterConstructors.makeEmptyRaster(1, 100, 
100, 5, 4, 3, -2, 0.1, 0.15, 3857);
+        Geometry envelope = GeometryFunctions.envelope(raster);
+        Envelope env = envelope.getEnvelopeInternal();
+        // The expected values were obtained by running the following query in 
PostGIS:
+        // SELECT ST_AsText(ST_Envelope(ST_MakeEmptyRaster(100, 100, 5, 4, 3, 
-2, 0.1, 0.15, 3857)));
+        assertEquals(5, env.getMinX(), 1e-9);
+        assertEquals(315, env.getMaxX(), 1e-9);
+        assertEquals(-196, env.getMinY(), 1e-9);
+        assertEquals(19, env.getMaxY(), 1e-9);
+
+        raster = RasterConstructors.makeEmptyRaster(1, 800, 700, 5, 4, 0.3, 
-0.2, -0.1, -0.15, 3857);
+        envelope = GeometryFunctions.envelope(raster);
+        env = envelope.getEnvelopeInternal();
+        // The expected values were obtained by running the following query in 
PostGIS:
+        // SELECT ST_AsText(ST_Envelope(ST_MakeEmptyRaster(800, 700, 5, 4, 
0.3, -0.2, -0.1, -0.15, 3857)));
+        assertEquals(-65, env.getMinX(), 1e-9);
+        assertEquals(245, env.getMaxX(), 1e-9);
+        assertEquals(-256, env.getMinY(), 1e-9);
+        assertEquals(4, env.getMaxY(), 1e-9);
+    }
+
+}
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 c6557c02..060c4838 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
@@ -20,8 +20,6 @@ package org.apache.sedona.common.raster;
 
 import org.geotools.coverage.grid.GridCoverage2D;
 import org.junit.Test;
-import org.locationtech.jts.geom.Envelope;
-import org.locationtech.jts.geom.Geometry;
 import org.opengis.referencing.FactoryException;
 
 import java.io.IOException;
@@ -30,38 +28,6 @@ import static org.junit.Assert.assertEquals;
 
 public class RasterAccessorsTest extends RasterTestBase
 {
-    @Test
-    public void envelope() throws FactoryException
-    {
-        Geometry envelope = RasterAccessors.envelope(oneBandRaster);
-        assertEquals(3600.0d, envelope.getArea(), 0.1d);
-        assertEquals(378922.0d + 30.0d, envelope.getCentroid().getX(), 0.1d);
-        assertEquals(4072345.0d + 30.0d, envelope.getCentroid().getY(), 0.1d);
-        assertEquals(4326, 
RasterAccessors.envelope(multiBandRaster).getSRID());
-    }
-
-    @Test
-    public void testEnvelopeUsingSkewedRaster() throws FactoryException {
-        GridCoverage2D raster = RasterConstructors.makeEmptyRaster(1, 100, 
100, 5, 4, 3, -2, 0.1, 0.15, 3857);
-        Geometry envelope = RasterAccessors.envelope(raster);
-        Envelope env = envelope.getEnvelopeInternal();
-        // The expected values were obtained by running the following query in 
PostGIS:
-        // SELECT ST_AsText(ST_Envelope(ST_MakeEmptyRaster(100, 100, 5, 4, 3, 
-2, 0.1, 0.15, 3857)));
-        assertEquals(5, env.getMinX(), 1e-9);
-        assertEquals(315, env.getMaxX(), 1e-9);
-        assertEquals(-196, env.getMinY(), 1e-9);
-        assertEquals(19, env.getMaxY(), 1e-9);
-
-        raster = RasterConstructors.makeEmptyRaster(1, 800, 700, 5, 4, 0.3, 
-0.2, -0.1, -0.15, 3857);
-        envelope = RasterAccessors.envelope(raster);
-        env = envelope.getEnvelopeInternal();
-        // The expected values were obtained by running the following query in 
PostGIS:
-        // SELECT ST_AsText(ST_Envelope(ST_MakeEmptyRaster(800, 700, 5, 4, 
0.3, -0.2, -0.1, -0.15, 3857)));
-        assertEquals(-65, env.getMinX(), 1e-9);
-        assertEquals(245, env.getMaxX(), 1e-9);
-        assertEquals(-256, env.getMinY(), 1e-9);
-        assertEquals(4, env.getMaxY(), 1e-9);
-    }
 
     @Test
     public void testNumBands() {
diff --git 
a/common/src/test/java/org/apache/sedona/common/raster/RasterConstructorsTest.java
 
b/common/src/test/java/org/apache/sedona/common/raster/RasterConstructorsTest.java
index 5c1c2a47..754810c9 100644
--- 
a/common/src/test/java/org/apache/sedona/common/raster/RasterConstructorsTest.java
+++ 
b/common/src/test/java/org/apache/sedona/common/raster/RasterConstructorsTest.java
@@ -30,7 +30,7 @@ public class RasterConstructorsTest
     public void fromArcInfoAsciiGrid() throws IOException, FactoryException {
         GridCoverage2D gridCoverage2D = 
RasterConstructors.fromArcInfoAsciiGrid(arc.getBytes(StandardCharsets.UTF_8));
 
-        Geometry envelope = RasterAccessors.envelope(gridCoverage2D);
+        Geometry envelope = GeometryFunctions.envelope(gridCoverage2D);
         assertEquals(3600, envelope.getArea(), 0.1);
         assertEquals(378922d + 30, envelope.getCentroid().getX(), 0.1);
         assertEquals(4072345d + 30, envelope.getCentroid().getY(), 0.1);
@@ -44,7 +44,7 @@ public class RasterConstructorsTest
     public void fromGeoTiff() throws IOException, FactoryException {
         GridCoverage2D gridCoverage2D = 
RasterConstructors.fromGeoTiff(geoTiff);
 
-        Geometry envelope = RasterAccessors.envelope(gridCoverage2D);
+        Geometry envelope = GeometryFunctions.envelope(gridCoverage2D);
         assertEquals(100, envelope.getArea(), 0.1);
         assertEquals(5, envelope.getCentroid().getX(), 0.1);
         assertEquals(5, envelope.getCentroid().getY(), 0.1);
@@ -64,7 +64,7 @@ public class RasterConstructorsTest
         int numBands = 1;
 
         GridCoverage2D gridCoverage2D = 
RasterConstructors.makeEmptyRaster(numBands, widthInPixel, heightInPixel, 
upperLeftX, upperLeftY, pixelSize);
-        Geometry envelope = RasterAccessors.envelope(gridCoverage2D);
+        Geometry envelope = GeometryFunctions.envelope(gridCoverage2D);
         assertEquals(upperLeftX, envelope.getEnvelopeInternal().getMinX(), 
0.001);
         assertEquals(upperLeftX + widthInPixel * pixelSize, 
envelope.getEnvelopeInternal().getMaxX(), 0.001);
         assertEquals(upperLeftY - heightInPixel * pixelSize, 
envelope.getEnvelopeInternal().getMinY(), 0.001);
@@ -81,7 +81,7 @@ public class RasterConstructorsTest
         assertEquals(1, gridCoverage2D.getNumSampleDimensions());
 
         gridCoverage2D = RasterConstructors.makeEmptyRaster(numBands, 
widthInPixel, heightInPixel, upperLeftX, upperLeftY, pixelSize, -pixelSize - 1, 
0, 0, 0);
-        envelope = RasterAccessors.envelope(gridCoverage2D);
+        envelope = GeometryFunctions.envelope(gridCoverage2D);
         assertEquals(upperLeftX, envelope.getEnvelopeInternal().getMinX(), 
0.001);
         assertEquals(upperLeftX + widthInPixel * pixelSize, 
envelope.getEnvelopeInternal().getMaxX(), 0.001);
         assertEquals(upperLeftY - heightInPixel * (pixelSize + 1), 
envelope.getEnvelopeInternal().getMinY(), 0.001);
diff --git a/docs/api/sql/Raster-operators.md b/docs/api/sql/Raster-operators.md
index 43528767..eae7ae12 100644
--- a/docs/api/sql/Raster-operators.md
+++ b/docs/api/sql/Raster-operators.md
@@ -32,6 +32,44 @@ Output:
 IndexOutOfBoundsException: Specified pixel coordinates (6, 2) do not lie in 
the raster
 ```
 
+## Geometry Functions
+
+### RS_Envelope
+
+Introduction: Returns the envelope of the raster as a Geometry.
+
+Format: `RS_Envelope (raster: Raster)`
+
+Since: `v1.4.0`
+
+Spark SQL example:
+```sql
+SELECT RS_Envelope(raster) FROM raster_table
+```
+Output:
+```
+POLYGON ((0 0,20 0,20 60,0 60,0 0))
+```
+
+### RS_ConvexHull
+
+Introduction: Return the convex hull geometry of the raster including the 
NoDataBandValue band pixels. 
+For regular shaped and non-skewed rasters, this gives more or less the same 
result as RS_ConvexHull and hence is only useful for irregularly shaped or 
skewed rasters.
+
+Format: `RS_ConvexHull(raster: Raster)`
+
+Since: `1.5.0`
+
+Spark SQL example:
+```sql
+SELECT RS_ConvexHull(SELECT RS_PixelAsPoint(RS_MakeEmptyRaster(1, 5, 10, 156, 
-132, 5, 10, 3, 5, 0)));
+```
+
+Output:
+```
+POLYGON ((156 -132, 181 -107, 211 -7, 186 -32, 156 -132))
+```
+
 ## Raster Accessors
 
 ### RS_Height
@@ -158,23 +196,6 @@ Output:
 
 ## Raster based operators
 
-### RS_Envelope
-
-Introduction: Returns the envelope of the raster as a Geometry.
-
-Format: `RS_Envelope (raster: Raster)`
-
-Since: `v1.4.0`
-
-Spark SQL example:
-```sql
-SELECT RS_Envelope(raster) FROM raster_table
-```
-Output:
-```
-POLYGON((0 0,20 0,20 60,0 60,0 0))
-```
-
 ### RS_Intersects
 
 Introduction: Returns true if the envelope of the raster intersects the given 
geometry. If the geometry does not have a
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 f9259db1..2886d93d 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
@@ -211,7 +211,8 @@ object Catalog {
     function[RS_UpperLeftY](),
     function[RS_ScaleX](),
     function[RS_ScaleY](),
-    function[RS_PixelAsPoint]()
+    function[RS_PixelAsPoint](),
+    function[RS_ConvexHull]()
   )
 
   val aggregateExpressions: Seq[Aggregator[Geometry, Geometry, Geometry]] = 
Seq(
diff --git 
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/GeometryFunctions.scala
 
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/GeometryFunctions.scala
new file mode 100644
index 00000000..d503c014
--- /dev/null
+++ 
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/GeometryFunctions.scala
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.spark.sql.sedona_sql.expressions.raster
+
+import org.apache.sedona.common.raster.GeometryFunctions
+import org.apache.spark.sql.catalyst.expressions.Expression
+import org.apache.spark.sql.sedona_sql.expressions.InferredExpression
+import 
org.apache.spark.sql.sedona_sql.expressions.InferrableFunctionConverter._
+
+case class RS_ConvexHull(inputExpressions: Seq[Expression]) extends 
InferredExpression(GeometryFunctions.convexHull _) {
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
+    copy(inputExpressions = newChildren)
+  }
+}
+
+case class RS_Envelope(inputExpressions: Seq[Expression]) extends 
InferredExpression(GeometryFunctions.envelope _) {
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
+    copy(inputExpressions = newChildren)
+  }
+}
+
diff --git 
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
 
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
index 39c45d8e..e1e9bd11 100644
--- 
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
+++ 
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterAccessors.scala
@@ -18,17 +18,11 @@
  */
 package org.apache.spark.sql.sedona_sql.expressions.raster
 
-import org.apache.sedona.common.raster.RasterAccessors
+import org.apache.sedona.common.raster.{GeometryFunctions, RasterAccessors}
 import org.apache.spark.sql.catalyst.expressions.Expression
 import 
org.apache.spark.sql.sedona_sql.expressions.InferrableFunctionConverter._
 import org.apache.spark.sql.sedona_sql.expressions.InferredExpression
 
-case class RS_Envelope(inputExpressions: Seq[Expression]) extends 
InferredExpression(RasterAccessors.envelope _) {
-  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
-    copy(inputExpressions = newChildren)
-  }
-}
-
 case class RS_NumBands(inputExpressions: Seq[Expression]) extends 
InferredExpression(RasterAccessors.numBands _) {
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
     copy(inputExpressions = newChildren)
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 5a80a2c0..7dd4e16e 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
@@ -21,7 +21,7 @@ package org.apache.sedona.sql
 import org.apache.spark.sql.functions.{collect_list, expr}
 import org.geotools.coverage.grid.GridCoverage2D
 import org.junit.Assert.assertEquals
-import org.locationtech.jts.geom.Geometry
+import org.locationtech.jts.geom.{Coordinate, Geometry}
 import org.scalatest.{BeforeAndAfter, GivenWhenThen}
 
 import scala.collection.mutable
@@ -517,6 +517,33 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       assertEquals(expected, result, 1e-9)
     }
 
+    it("Passed RS_ConvexHull with raster") {
+      val df = sparkSession.read.format("binaryFile").load(resourceFolder + 
"raster/test1.tiff")
+      val result = 
df.selectExpr("RS_ConvexHull(RS_FromGeoTiff(content))").first().getAs[Geometry](0);
+      val coordinates = result.getCoordinates;
+
+      val expectedCoordOne = new Coordinate(-13095817.809482181, 
4021262.7487925636)
+      val expectedCoordTwo = new Coordinate(-13058785.559768861, 
4021262.7487925636)
+      val expectedCoordThree = new Coordinate(-13058785.559768861, 
3983868.8560156375)
+      val expectedCoordFour = new Coordinate(-13095817.809482181, 
3983868.8560156375)
+
+      assertEquals(expectedCoordOne.getX, coordinates(0).getX, 0.5d)
+      assertEquals(expectedCoordOne.getY, coordinates(0).getY, 0.5d)
+
+      assertEquals(expectedCoordTwo.getX, coordinates(1).getX, 0.5d)
+      assertEquals(expectedCoordTwo.getY, coordinates(1).getY, 0.5d)
+
+      assertEquals(expectedCoordThree.getX, coordinates(2).getX, 0.5d)
+      assertEquals(expectedCoordThree.getY, coordinates(2).getY, 0.5d)
+
+      assertEquals(expectedCoordFour.getX, coordinates(3).getX, 0.5d)
+      assertEquals(expectedCoordFour.getY, coordinates(3).getY, 0.5d)
+
+      assertEquals(expectedCoordOne.getX, coordinates(4).getX, 0.5d)
+      assertEquals(expectedCoordOne.getY, coordinates(4).getY, 0.5d)
+
+    }
+
     it("Passed RS_PixelAsPoint with raster") {
       val widthInPixel = 5
       val heightInPixel = 10

Reply via email to