Skip to content

Commit

Permalink
ConstantTile with NoData: support idempotent CellType conversions (#3553
Browse files Browse the repository at this point in the history
)

* ConstantTile with nodata: support correct celltype conversion
#3525

* Fix all of the constant cellType conversions

* CHANGELOG.md update
---------

Co-authored-by: Jeroen Dries <[email protected]>
  • Loading branch information
pomadchin and jdries authored Oct 28, 2024
1 parent 5f82505 commit 01a320a
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dependecies update & fix "not found: type Serializable" compiler bug [#3535](https://github.com/locationtech/geotrellis/pull/3535)
- Update GDAL up to 3.9.x [#3540](https://github.com/locationtech/geotrellis/pull/3540)
- Fix reprojection with downsampling for GeotiffRasterSource and tile RDDs. Reprojection outside the valid projection bounds may now throw a GeoAttrsError. [#3541](https://github.com/locationtech/geotrellis/issues/3541)
- ConstantTile with NoData: support idempotent CellType conversions [#3553](https://github.com/locationtech/geotrellis/pull/3553)

## [3.7.1] - 2024-01-08

Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.10.2
sbt.version=1.10.3
53 changes: 43 additions & 10 deletions raster/src/main/scala/geotrellis/raster/ConstantTile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package geotrellis.raster
import geotrellis.vector.Extent

import java.nio.ByteBuffer

import spire.syntax.cfor._


Expand All @@ -37,10 +36,10 @@ abstract class ConstantTile extends Tile {
def rows: Int

/** Precomputed view of tile cells as seen by [[get]] method */
protected val iVal: Int
protected def iVal: Int

/** Precomputed view of tile cells as seen by [[getDouble]] method */
protected val dVal: Double
protected def dVal: Double

/**
* Fetch the datum at the given column and row of the tile.
Expand Down Expand Up @@ -81,18 +80,17 @@ 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 = {
def convert(newType: CellType): Tile =
newType match {
case BitCellType => new BitConstantTile(if (iVal == 0) false else true, cols, rows)
case ct: ByteCells => ByteConstantTile(iVal.toByte, cols, rows, ct)
case ct: ByteCells => ByteConstantTile(i2b(iVal), cols, rows, ct)
case ct: UByteCells => UByteConstantTile(iVal.toByte, cols, rows, ct)
case ct: ShortCells => ShortConstantTile(iVal.toShort , cols, rows, ct)
case ct: UShortCells => UShortConstantTile(iVal.toShort , cols, rows, ct)
case ct: IntCells => IntConstantTile(iVal , cols, rows, ct)
case ct: FloatCells => FloatConstantTile(dVal.toFloat , 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)
Expand Down Expand Up @@ -243,6 +241,41 @@ object ConstantTile {
case ct: FloatCells => FloatConstantTile.fromBytes(bytes, cols, rows, ct)
case ct: DoubleCells => DoubleConstantTile.fromBytes(bytes, cols, rows, ct)
}

/**
* Create a [[ConstantTile]] that is set to the nodata value of the given celltype or to the default nodata value if
* the celltype does not have a nodata. BitCells are set to 'false'.
*
* @param t The [[CellType]] of the new [[ConstantTile]].
* @param cols The number of columns that the new [[ConstantTile]] should have
* @param rows The number of rows that the new [[ConstantTile]] should have
* @return The new [[ConstantTile]]
*/
def empty(t: CellType, cols: Int, rows: Int): Tile =
t match {
case _: BitCells => BitConstantTile(v = false, cols, rows)
case ct: ByteUserDefinedNoDataCellType => ByteConstantTile(ct.noDataValue, cols, rows, ct)
case ct: ByteConstantNoDataCellType.type => ByteConstantTile(ct.noDataValue, cols, rows, ct)
case ct: ByteCellType.type => ByteConstantTile(byteNODATA, cols, rows, ct)
case ct: UByteConstantNoDataCellType.type => UByteConstantTile(ct.noDataValue, cols, rows, ct)
case ct: UByteUserDefinedNoDataCellType => UByteConstantTile(ct.noDataValue, cols, rows, ct)
case ct: UByteCellType.type => UByteConstantTile(ubyteNODATA, cols, rows, ct)
case ct: ShortUserDefinedNoDataCellType => ShortConstantTile(ct.noDataValue, cols, rows, ct)
case ct: ShortConstantNoDataCellType.type => ShortConstantTile(ct.noDataValue, cols, rows, ct)
case ct: ShortCellType.type => ShortConstantTile(shortNODATA, cols, rows, ct)
case ct: UShortUserDefinedNoDataCellType => UShortConstantTile(ct.noDataValue, cols, rows, ct)
case ct: UShortConstantNoDataCellType.type => UShortConstantTile(ct.noDataValue, cols, rows, ct)
case ct: UShortCellType.type => UShortConstantTile(ushortNODATA, cols, rows, ct)
case ct: IntUserDefinedNoDataCellType => IntConstantTile(ct.noDataValue, cols, rows, ct)
case ct: IntConstantNoDataCellType.type => IntConstantTile(ct.noDataValue, cols, rows, ct)
case ct: IntCellType.type => IntConstantTile(NODATA, cols, rows, ct)
case ct: FloatUserDefinedNoDataCellType => FloatConstantTile(ct.noDataValue, cols, rows, ct)
case ct: FloatConstantNoDataCellType.type => FloatConstantTile(ct.noDataValue, cols, rows, ct)
case ct: FloatCellType.type => FloatConstantTile(floatNODATA, cols, rows, ct)
case ct: DoubleUserDefinedNoDataCellType => DoubleConstantTile(ct.noDataValue, cols, rows, ct)
case ct: DoubleConstantNoDataCellType.type => DoubleConstantTile(ct.noDataValue, cols, rows, ct)
case ct: DoubleCellType.type => DoubleConstantTile(doubleNODATA, cols, rows, ct)
}
}

/**
Expand Down
60 changes: 59 additions & 1 deletion raster/src/test/scala/geotrellis/raster/ConstantTileSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package geotrellis.raster

import geotrellis.raster.testkit.{RasterMatchers, TileBuilders}

import org.scalatest.matchers.should.Matchers
import org.scalatest.funspec.AnyFunSpec

Expand Down Expand Up @@ -93,4 +92,63 @@ class ConstantTileSpec extends AnyFunSpec with Matchers with RasterMatchers with
int.combine(dt)(_ + _).cellType shouldBe int.cellType.union(dt.cellType)
}
}

describe("CellType conversion idempotence") {
List(
ByteConstantTile(byteNODATA, cols = 1, rows = 1),
UByteConstantTile(ubyteNODATA, cols = 1, rows = 1),
ShortConstantTile(shortNODATA, cols = 1, rows = 1),
UShortConstantTile(ushortNODATA, cols = 1, rows = 1),
IntConstantTile(NODATA, cols = 1, rows = 1),
FloatConstantTile(Float.NaN, cols = 1, rows = 1),
DoubleConstantTile(Double.NaN, cols = 1, rows = 1)
).foreach { tile =>
val className = getClassName(tile)
it(s"should convert empty $className to empty $className") {
assert(tile.isNoDataTile)
assert(tile.convert(tile.cellType).isNoDataTile)
}
}

it("should convert empty BitConstantTile to empty BitConstantTile") {
val tile = BitConstantTile(v = false, cols = 1, rows = 1)
tile.get(0, 0) shouldBe 0
tile.convert(tile.cellType).get(0, 0) shouldBe 0
}
}

describe("create empty tiles of CellTypes that support NoData") {
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 create empty tile for $cellType") {
val tile = ConstantTile.empty(cellType, 1, 1)
assert(tile.isNoDataTile)
assert(tile.cellType == cellType)
}
}
}

private def getClassName[T](obj: T): String = obj.getClass.getName.split("\\.").last
}

0 comments on commit 01a320a

Please sign in to comment.