Skip to content

Commit

Permalink
[SEDONA-353] Add RS_BandNoDataValue (#958)
Browse files Browse the repository at this point in the history
  • Loading branch information
iGN5117 authored Aug 11, 2023
1 parent f7e9e0b commit b6d160a
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;

public class RasterBandAccessors {

public static Double getBandNoDataValue(GridCoverage2D raster, int band) {
if (band > raster.getNumSampleDimensions()) {
throw new IllegalArgumentException("Provided band index is not present in the raster");
}
GridSampleDimension bandSampleDimension = raster.getSampleDimension(band - 1);
if (bandSampleDimension.getNoDataValues() == null) return null;
return raster.getSampleDimension(band - 1).getNoDataValues()[0];
}

public static Double getBandNoDataValue(GridCoverage2D raster) {
return getBandNoDataValue(raster, 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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.geotools.coverage.grid.GridCoverage2D;
import org.junit.Test;
import org.opengis.referencing.FactoryException;

import java.io.IOException;

import static org.junit.Assert.*;

public class RasterBandAccessorsTest extends RasterTestBase {

@Test
public void testBandNoDataValueCustomBand() throws FactoryException {
int width = 5, height = 10;
GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, width, height, 53, 51, 1, 1, 0, 0, 4326);
double[] values = new double[width * height];
for (int i = 0; i < values.length; i++) {
values[i] = i + 1;
}
emptyRaster = MapAlgebra.addBandFromArray(emptyRaster, values, 2, 1d);
assertNotNull(RasterBandAccessors.getBandNoDataValue(emptyRaster, 2));
assertEquals(1, RasterBandAccessors.getBandNoDataValue(emptyRaster, 2), 1e-9);
assertNull(RasterBandAccessors.getBandNoDataValue(emptyRaster));
}

@Test
public void testBandNoDataValueDefaultBand() throws FactoryException {
int width = 5, height = 10;
GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, width, height, 53, 51, 1, 1, 0, 0, 4326);
double[] values = new double[width * height];
for (int i = 0; i < values.length; i++) {
values[i] = i + 1;
}
emptyRaster = MapAlgebra.addBandFromArray(emptyRaster, values, 1, 1d);
assertNotNull(RasterBandAccessors.getBandNoDataValue(emptyRaster));
assertEquals(1, RasterBandAccessors.getBandNoDataValue(emptyRaster), 1e-9);
}

@Test
public void testBandNoDataValueDefaultNoData() throws FactoryException {
int width = 5, height = 10;
GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, width, height, 53, 51, 1, 1, 0, 0, 4326);
double[] values = new double[width * height];
for (int i = 0; i < values.length; i++) {
values[i] = i + 1;
}
assertNull(RasterBandAccessors.getBandNoDataValue(emptyRaster, 1));
}

@Test
public void testBandNoDataValueIllegalBand() throws FactoryException, IOException {
GridCoverage2D raster = rasterFromGeoTiff(resourceFolder + "raster/raster_with_no_data/test5.tiff");
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> RasterBandAccessors.getBandNoDataValue(raster, 2));
assertEquals("Provided band index is not present in the raster", exception.getMessage());
}

}
32 changes: 32 additions & 0 deletions docs/api/sql/Raster-operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,38 @@ Output: `3`
!!!Tip
For non-skewed rasters, you can provide any value for longitude and the intended value of world latitude, to get the desired answer

## Raster Band Accessors

### RS_BandNoDataValue

Introduction: Returns the no data value of the given band of the given raster. If no band is given, band 1 is assumed. The band parameter is 1-indexed. If there is no no data value associated with the given band, RS_BandNoDataValue returns null.

!!!Note
If the given band does not lie in the raster, RS_BandNoDataValue throws an IllegalArgumentException

Format: `RS_BandNoDataValue (raster: Raster, band: Int = 1)`

Since: `1.5.0`

Spark SQL example:
```sql
SELECT RS_BandNoDataValue(raster, 1) from rasters;
```

Output: `0.0`

```sql
SELECT RS_BandNoDataValue(raster) from rasters_without_nodata;
```

Output: `null`

```sql
SELECT RS_BandNoDataValue(raster, 3) from rasters;
```

Output: `IllegalArgumentException: Provided band index is not present in the raster.`


## Raster based operators

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ object Catalog {
function[RS_Contains](),
function[RS_WorldToRasterCoord](),
function[RS_WorldToRasterCoordX](),
function[RS_WorldToRasterCoordY]()
function[RS_WorldToRasterCoordY](),
function[RS_BandNoDataValue]()
)

val aggregateExpressions: Seq[Aggregator[Geometry, Geometry, Geometry]] = Seq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
package org.apache.spark.sql.sedona_sql.expressions.raster

import org.apache.sedona.common.raster.{GeometryFunctions, RasterAccessors}
import org.apache.sedona.common.raster.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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.RasterBandAccessors
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_BandNoDataValue(inputExpressions: Seq[Expression]) extends InferredExpression(inferrableFunction2(RasterBandAccessors.getBandNoDataValue), inferrableFunction1(RasterBandAccessors.getBandNoDataValue)) {
protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
copy(inputExpressions = newChildren)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ package org.apache.sedona.sql
import org.apache.spark.sql.connector.catalog.TableChange.ColumnPosition.first
import org.apache.spark.sql.functions.{collect_list, expr}
import org.geotools.coverage.grid.GridCoverage2D
import org.junit.Assert.assertEquals
import org.junit.Assert.{assertEquals, assertNull}
import org.locationtech.jts.geom.{Coordinate, Geometry}
import org.scalatest.{BeforeAndAfter, GivenWhenThen}

Expand Down Expand Up @@ -671,5 +671,27 @@ class rasteralgebraTest extends TestBaseScala with BeforeAndAfter with GivenWhen
assert(sparkSession.sql("SELECT RS_WITHIN(RS_MakeEmptyRaster(1, 20, 20, 2, 22, 1), ST_GeomFromWKT('POLYGON ((0 0, 0 50, 100 50, 100 0, 0 0))'))").first().getBoolean(0))
assert(!sparkSession.sql("SELECT RS_WITHIN(RS_MakeEmptyRaster(1, 100, 100, 0, 50, 1), ST_GeomFromWKT('POLYGON ((2 2, 2 25, 20 25, 20 2, 2 2))'))").first().getBoolean(0))
}

it("Passed RS_BandNoDataValue - noDataValueFor for raster from geotiff - default band") {
var df = sparkSession.read.format("binaryFile").load(resourceFolder + "raster/raster_with_no_data/test5.tiff")
df = df.selectExpr("RS_FromGeoTiff(content) as raster")
val result = df.selectExpr("RS_BandNoDataValue(raster)").first().getDouble(0)
assertEquals(0, result, 1e-9)
}

it("Passed RS_BandNoDataValue - null noDataValueFor for raster from geotiff") {
var df = sparkSession.read.format("binaryFile").load(resourceFolder + "raster/test1.tiff")
df = df.selectExpr("RS_FromGeoTiff(content) as raster")
val result = df.selectExpr("RS_BandNoDataValue(raster)").first().get(0)
assertNull(result)
}

it("Passed RS_BandNoDataValue - noDataValueFor for raster from geotiff - explicit band") {
var df = sparkSession.read.format("binaryFile").load(resourceFolder + "raster/raster_with_no_data/test5.tiff")
df = df.selectExpr("RS_FromGeoTiff(content) as raster")
val result = df.selectExpr("RS_BandNoDataValue(raster, 1)").first().getDouble(0)
assertEquals(0, result, 1e-9)
}

}
}

0 comments on commit b6d160a

Please sign in to comment.