diff --git a/CHANGELOG.md b/CHANGELOG.md index 4de9354a0f..b3e1477c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - MinResample should ignore Int NODATA values [#3590](https://github.com/locationtech/geotrellis/pull/3590) +- Empty (NoData) ConstantTile objects are now correctly converted to empty ConstantTile objects of the desired CellType ## [3.8.0] - 2025-04-23 diff --git a/raster/src/main/scala/geotrellis/raster/ConstantTile.scala b/raster/src/main/scala/geotrellis/raster/ConstantTile.scala index 15a43fe70c..5b8ac94f16 100644 --- a/raster/src/main/scala/geotrellis/raster/ConstantTile.scala +++ b/raster/src/main/scala/geotrellis/raster/ConstantTile.scala @@ -80,17 +80,22 @@ abstract class ConstantTile extends Tile { * @param newType The type of cells that the result should have * @return The new Tile */ - def convert(newType: CellType): Tile = - newType match { - case BitCellType => new BitConstantTile(if (iVal == 0) false else true, cols, rows) - case ct: ByteCells => ByteConstantTile(i2b(iVal), cols, rows, ct) - case ct: UByteCells => UByteConstantTile(iVal.toByte, cols, rows, ct) - case ct: ShortCells => ShortConstantTile(i2s(iVal), cols, rows, ct) - case ct: UShortCells => UShortConstantTile(i2us(iVal) , cols, rows, ct) - case ct: IntCells => IntConstantTile(iVal, cols, rows, ct) - case ct: FloatCells => FloatConstantTile(d2f(dVal), cols, rows, ct) - case ct: DoubleCells => DoubleConstantTile(dVal, cols, rows, ct) + def convert(newType: CellType): Tile = { + if (isNoDataTile) { + ConstantTile.empty(newType, cols, rows) + } else { + newType match { + case BitCellType => new BitConstantTile(if (iVal == 0) false else true, cols, rows) + case ct: ByteCells => ByteConstantTile(i2b(iVal), cols, rows, ct) + case ct: UByteCells => UByteConstantTile(iVal.toByte, cols, rows, ct) + case ct: ShortCells => ShortConstantTile(i2s(iVal), cols, rows, ct) + case ct: UShortCells => UShortConstantTile(i2us(iVal) , cols, rows, ct) + case ct: IntCells => IntConstantTile(iVal, cols, rows, ct) + case ct: FloatCells => FloatConstantTile(d2f(dVal), cols, rows, ct) + case ct: DoubleCells => DoubleConstantTile(dVal, cols, rows, ct) + } } + } def interpretAs(newCellType: CellType): Tile = withNoData(None).convert(newCellType) @@ -218,6 +223,14 @@ abstract class ConstantTile extends Tile { } tile } + + override def isNoDataTile: Boolean = { + if (cellType.isFloatingPoint) { + !isData(getDouble(0, 0)) + } else { + !isData(get(0, 0)) + } + } } object ConstantTile { diff --git a/raster/src/test/scala/geotrellis/raster/ConstantTileSpec.scala b/raster/src/test/scala/geotrellis/raster/ConstantTileSpec.scala index 2c7586ff46..41399b92ab 100644 --- a/raster/src/test/scala/geotrellis/raster/ConstantTileSpec.scala +++ b/raster/src/test/scala/geotrellis/raster/ConstantTileSpec.scala @@ -150,5 +150,43 @@ class ConstantTileSpec extends AnyFunSpec with Matchers with RasterMatchers with } } + describe("conversion of empty tiles of CellTypes that support NoData should result in NoData tiles") { + List( + // BitCellType, + ByteUserDefinedNoDataCellType(1.toByte), + ByteConstantNoDataCellType, + // ByteCellType, + UByteConstantNoDataCellType, + UByteUserDefinedNoDataCellType(1.toByte), + // UByteCellType, + ShortUserDefinedNoDataCellType(1.toShort), + ShortConstantNoDataCellType, + // ShortCellType, + UShortUserDefinedNoDataCellType(1.toShort), + UShortConstantNoDataCellType, + // UShortCellType, + IntUserDefinedNoDataCellType(1), + IntConstantNoDataCellType, + // IntCellType, + FloatUserDefinedNoDataCellType(1.0f), + FloatConstantNoDataCellType, + // FloatCellType, + DoubleUserDefinedNoDataCellType(1.0), + DoubleConstantNoDataCellType, + // DoubleCellType + ).foreach { cellType => + it(s"should convert an empty tile of $cellType to an empty tile of the new cell type") { + val tile = ConstantTile.empty(cellType, 1, 1) + assert(tile.isNoDataTile) + assert(tile.cellType == cellType) + val newCellType = FloatUserDefinedNoDataCellType(666.0f) + val convertedNoDataTile = tile.convert(newCellType) + assert(convertedNoDataTile.isNoDataTile) + assert(convertedNoDataTile.cellType == newCellType) + } + } + } + + private def getClassName[T](obj: T): String = obj.getClass.getName.split("\\.").last }