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 f41d908cc [SEDONA-448] RS_SetBandNoDataValue should have `replace` 
option (#1160)
f41d908cc is described below

commit f41d908ccbf4f7bb01196fc9e0f42c334e464b8a
Author: Furqaan Khan <[email protected]>
AuthorDate: Thu Dec 21 21:18:29 2023 -0500

    [SEDONA-448] RS_SetBandNoDataValue should have `replace` option (#1160)
    
    * feat: add replace option, not implementation
    
    * feat: add replace option and testing
    
    * feat: port new function to spark
    
    * docs: add docs for replace option
    
    * chore: clean imports
    
    * refactor: remove duplicate variable
---
 .../sedona/common/raster/RasterBandEditors.java    | 50 ++++++++++++-----
 .../common/raster/RasterBandEditorsTest.java       | 62 ++++++++++++++++++++--
 docs/api/sql/Raster-operators.md                   | 19 ++++++-
 .../expressions/raster/RasterBandEditors.scala     |  1 +
 4 files changed, 113 insertions(+), 19 deletions(-)

diff --git 
a/common/src/main/java/org/apache/sedona/common/raster/RasterBandEditors.java 
b/common/src/main/java/org/apache/sedona/common/raster/RasterBandEditors.java
index bcc471dc3..4adf36c65 100644
--- 
a/common/src/main/java/org/apache/sedona/common/raster/RasterBandEditors.java
+++ 
b/common/src/main/java/org/apache/sedona/common/raster/RasterBandEditors.java
@@ -23,12 +23,8 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.sedona.common.utils.RasterUtils;
 import org.geotools.coverage.GridSampleDimension;
 import org.geotools.coverage.grid.GridCoverage2D;
-import org.geotools.coverage.grid.GridEnvelope2D;
-import org.geotools.coverage.grid.GridGeometry2D;
 import org.geotools.coverage.processing.operation.Crop;
-import org.geotools.referencing.operation.transform.AffineTransform2D;
 import org.locationtech.jts.geom.Geometry;
-import org.opengis.metadata.spatial.PixelOrientation;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.operation.TransformException;
@@ -46,9 +42,10 @@ public class RasterBandEditors {
      * @param raster Source raster to add no-data value
      * @param bandIndex Band index to add no-data value
      * @param noDataValue Value to set as no-data value, if null then remove 
existing no-data value
+     * @param replace if true replaces the previous no-data value with the 
specified no-data value
      * @return Raster with no-data value
      */
-    public static GridCoverage2D setBandNoDataValue(GridCoverage2D raster, int 
bandIndex, Double noDataValue) {
+    public static GridCoverage2D setBandNoDataValue(GridCoverage2D raster, int 
bandIndex, Double noDataValue, boolean replace) {
         RasterUtils.ensureBand(raster, bandIndex);
         Double rasterNoData = RasterBandAccessors.getBandNoDataValue(raster, 
bandIndex);
 
@@ -62,22 +59,47 @@ public class RasterBandEditors {
             return RasterUtils.clone(raster.getRenderedImage(), null, 
sampleDimensions, raster, null, true);
         }
 
-        if ( !(rasterNoData == null) && rasterNoData.equals(noDataValue)) {
+        if ( rasterNoData != null && rasterNoData.equals(noDataValue)) {
             return raster;
         }
         GridSampleDimension[] bands = raster.getSampleDimensions();
         bands[bandIndex - 1] = 
RasterUtils.createSampleDimensionWithNoDataValue(bands[bandIndex - 1], 
noDataValue);
 
-        int width = RasterAccessors.getWidth(raster), height = 
RasterAccessors.getHeight(raster);
-        AffineTransform2D affine = RasterUtils.getGDALAffineTransform(raster);
-        GridGeometry2D gridGeometry2D = new GridGeometry2D(
-                new GridEnvelope2D(0, 0, width, height),
-                PixelOrientation.UPPER_LEFT,
-                affine, raster.getCoordinateReferenceSystem2D(), null
-        );
+        if (replace) {
+            if (rasterNoData == null) {
+                throw new IllegalArgumentException("The raster provided 
doesn't have a no-data value. Please provide a raster that has a no-data value 
to use `replace` option.");
+            }
+
+            Raster rasterData = 
RasterUtils.getRaster(raster.getRenderedImage());
+            int dataTypeCode = rasterData.getDataBuffer().getDataType();
+            int numBands = RasterAccessors.numBands(raster);
+            int height = RasterAccessors.getHeight(raster);
+            int width = RasterAccessors.getWidth(raster);
+            WritableRaster wr = RasterFactory.createBandedRaster(dataTypeCode, 
width, height, numBands, null);
+            double[] bandData = rasterData.getSamples(0, 0, width, height, 
bandIndex - 1, (double[]) null);
+            for (int i = 0; i < bandData.length; i++) {
+                if (bandData[i] == rasterNoData) {
+                    bandData[i] = noDataValue;
+                }
+            }
+            wr.setSamples(0, 0, width, height, bandIndex - 1, bandData);
+            return RasterUtils.clone(wr, null, bands, raster, null, true);
+        }
+
         return RasterUtils.clone(raster.getRenderedImage(), null, bands, 
raster, null, true);
     }
 
+    /**
+     * Adds no-data value to the raster.
+     * @param raster Source raster to add no-data value
+     * @param bandIndex Band index to add no-data value
+     * @param noDataValue Value to set as no-data value, if null then remove 
existing no-data value
+     * @return Raster with no-data value
+     */
+    public static GridCoverage2D setBandNoDataValue(GridCoverage2D raster, int 
bandIndex, Double noDataValue) {
+        return setBandNoDataValue(raster, bandIndex, noDataValue, false);
+    }
+
     /**
      * Adds no-data value to the raster.
      * @param raster Source raster to add no-data value
@@ -85,7 +107,7 @@ public class RasterBandEditors {
      * @return Raster with no-data value
      */
     public static GridCoverage2D setBandNoDataValue(GridCoverage2D raster, 
Double noDataValue) {
-        return setBandNoDataValue(raster, 1, noDataValue);
+        return setBandNoDataValue(raster, 1, noDataValue, false);
     }
 
     /**
diff --git 
a/common/src/test/java/org/apache/sedona/common/raster/RasterBandEditorsTest.java
 
b/common/src/test/java/org/apache/sedona/common/raster/RasterBandEditorsTest.java
index fd4ad5688..e1a8715f1 100644
--- 
a/common/src/test/java/org/apache/sedona/common/raster/RasterBandEditorsTest.java
+++ 
b/common/src/test/java/org/apache/sedona/common/raster/RasterBandEditorsTest.java
@@ -19,9 +19,6 @@
 package org.apache.sedona.common.raster;
 
 import org.apache.sedona.common.Constructors;
-import org.apache.sedona.common.Functions;
-import org.apache.sedona.common.FunctionsGeoTools;
-import org.apache.sedona.common.utils.RasterUtils;
 import org.geotools.coverage.grid.GridCoverage2D;
 import org.junit.Test;
 import org.locationtech.jts.geom.Geometry;
@@ -29,11 +26,14 @@ import org.locationtech.jts.io.ParseException;
 import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.operation.TransformException;
 
-import java.awt.geom.Point2D;
 import java.io.IOException;
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 import static org.junit.Assert.*;
 
@@ -64,6 +64,60 @@ public class RasterBandEditorsTest extends RasterTestBase{
         assertEquals(expected, actual);
     }
 
+    @Test
+    public void testSetBandNoDataValueWithReplaceOptionRaster() throws 
IOException {
+        GridCoverage2D raster = rasterFromGeoTiff(resourceFolder + 
"raster/raster_with_no_data/test5.tiff");
+        double[] originalSummary = RasterBandAccessors.getSummaryStats(raster, 
1, false);
+        int sumOG = (int) originalSummary[1];
+
+        assertEquals(206233487, sumOG);
+        GridCoverage2D resultRaster = 
RasterBandEditors.setBandNoDataValue(raster, 1, 10.0, true);
+        double[] resultSummary = 
RasterBandAccessors.getSummaryStats(resultRaster, 1, false);
+        int sumActual = (int) resultSummary[1];
+
+        // 108608 is the total no-data values in the raster
+        // 10.0 is the new no-data value
+        int sumExpected = sumOG + (10 * 108608);
+        assertEquals(sumExpected, sumActual);
+
+        // Not replacing previous no-data value
+        resultRaster = RasterBandEditors.setBandNoDataValue(raster, 1, 10.0);
+        resultSummary = RasterBandAccessors.getSummaryStats(resultRaster, 1, 
false);
+        sumActual = (int) resultSummary[1];
+        assertEquals(sumOG, sumActual);
+    }
+
+    @Test
+    public void testSetBandNoDataValueWithReplaceOption() throws 
FactoryException {
+        GridCoverage2D raster = RasterConstructors.makeEmptyRaster(1, "d", 10, 
20, 10, 20, 1);
+        double[] band1 = new double[200];
+        DecimalFormat df = new DecimalFormat("0.00");
+        for (int i = 0; i < band1.length; i++) {
+            if (i % 3 == 0) {
+                band1[i] = 15;
+                continue;
+            }
+            band1[i] = Double.parseDouble(df.format(Math.random() * 10));
+        }
+        raster = MapAlgebra.addBandFromArray(raster, band1, 1);
+        // setting the noData property
+        raster = RasterBandEditors.setBandNoDataValue(raster, 1, 15.0);
+
+        // invoking replace option.
+        GridCoverage2D result = RasterBandEditors.setBandNoDataValue(raster, 
1, 20.0, true);
+        double[] resultBand = MapAlgebra.bandAsArray(result, 1);
+
+        Map<Double, Long> resultMap = Arrays.stream(resultBand)
+                .boxed()
+                .collect(Collectors.groupingBy(Function.identity(), 
Collectors.counting()));
+
+        Map<Double, Long> actualMap = Arrays.stream(band1)
+                .boxed()
+                .collect(Collectors.groupingBy(Function.identity(), 
Collectors.counting()));
+
+        assertEquals(actualMap.get(15.0), resultMap.get(20.0));
+    }
+
     @Test
     public void testSetBandNoDataValueWithEmptyRaster() throws 
FactoryException {
         GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, 20, 
20, 0, 0, 8, 8, 0.1, 0.1, 4326);
diff --git a/docs/api/sql/Raster-operators.md b/docs/api/sql/Raster-operators.md
index 953849897..ca161b720 100644
--- a/docs/api/sql/Raster-operators.md
+++ b/docs/api/sql/Raster-operators.md
@@ -1583,7 +1583,24 @@ Output:
 
 Introduction: This sets the no data value for a specified band in the raster. 
If the band index is not provided, band 1 is assumed by default. Passing a 
`null` value for `noDataValue` will remove the no data value and that will 
ensure all pixels are included in functions rather than excluded as no data.
 
-Format: `RS_SetBandNoDataValue(raster: Raster, bandIndex: Integer = 1, 
noDataValue: Double)`
+Since `v1.5.1`, this function supports the ability to replace the current 
no-data value with the new `noDataValue`. 
+
+!!!Note
+    When `replace` is true, any pixels matching the provided `noDataValue` 
will be considered as no-data in the output raster.
+
+    An `IllegalArgumentException` will be thrown if the input raster does not 
already have a no-data value defined. Replacing existing values with 
`noDataValue` requires a defined no-data baseline to evaluate against.
+
+    To use this for no-data replacement, the input raster must first set its 
no-data value, which can then be selectively replaced via this function.
+
+Format: 
+
+```
+RS_SetBandNoDataValue(raster: Raster, bandIndex: Integer, noDataValue: Double, 
replace: Boolean)
+```
+
+```
+RS_SetBandNoDataValue(raster: Raster, bandIndex: Integer = 1, noDataValue: 
Double)
+```
 
 Since: `v1.5.0`
 
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterBandEditors.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterBandEditors.scala
index 00a45dc25..de782a888 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterBandEditors.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/raster/RasterBandEditors.scala
@@ -24,6 +24,7 @@ import 
org.apache.spark.sql.sedona_sql.expressions.InferrableFunctionConverter._
 import org.apache.spark.sql.sedona_sql.expressions.InferredExpression
 
 case class RS_SetBandNoDataValue(inputExpressions: Seq[Expression]) extends 
InferredExpression(
+  inferrableFunction4(RasterBandEditors.setBandNoDataValue),
   inferrableFunction3(RasterBandEditors.setBandNoDataValue),
   inferrableFunction2(RasterBandEditors.setBandNoDataValue)
 ) {

Reply via email to