Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interpretAs and withNoData methods to Tile #1702

Merged
merged 9 commits into from
Oct 30, 2016
85 changes: 85 additions & 0 deletions raster-test/src/test/scala/geotrellis/raster/ArrayTileSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,90 @@ class ArrayTileSpec extends FunSpec
it("should convert to a DoubleCellType") { checkDouble(DoubleCellType, Array(0, 1, -1, Double.NaN)) }
it("should convert to a DoubleConstantNoDataCellType") { checkDouble(DoubleConstantNoDataCellType, Array(0, 1, -1, Double.NaN)) }
it("should convert to a DoubleUserDefinedNoDataCellType") { checkDouble(DoubleUserDefinedNoDataCellType(1), Array(0, Double.NaN, -1, Double.NaN)) }


/*
The critical aspect of Tile.interpretAs is that as long as type conversion does not truncate value
the interpretations of NoData value will not alter the underlying cell values as happens with Tile.convert
*/

def checkFloatInterpretAs(tile: Tile, udCt: Double => CellType, constCt: CellType) = {
for {
r <- 0 until tile.rows
c <- 0 until tile.cols
} {
val v = tile.get(c, r)
val udTile = tile.interpretAs(udCt(v))
val constTile = udTile.interpretAs(constCt)
val res = constTile.withNoData(None)
withClue(s"ND=$v") { assert(res equals tile) }
val cell = udTile.getDouble(c,r)
withClue(s"udTile($c, $r), ND=$v") { assert(isNoData(cell)) }
}
}

def checkIntInterpretAs(tile: Tile, udCt: Int => CellType, constCt: CellType) = {
for {
r <- 0 until tile.rows
c <- 0 until tile.cols
} {
val v = tile.get(c, r)
val udTile = tile.interpretAs(udCt(v))
val constTile = udTile.interpretAs(constCt)
val res = constTile.withNoData(None)
withClue(s"ND=$v") { assert(res equals tile) }
val cell = udTile.get(c,r)
withClue(s"udTile($c, $r), ND=$v") { assert(isNoData(cell)) }
}
}

it("should interpretAs for DoubleCells") {
checkFloatInterpretAs(
sourceTile,
DoubleUserDefinedNoDataCellType,
DoubleConstantNoDataCellType)
}

it("should interpretAs for FloatCells") {
checkFloatInterpretAs(
sourceTile.convert(FloatCellType),
x => FloatUserDefinedNoDataCellType(x.toFloat),
FloatConstantNoDataCellType)
}

it("should interpretAs for IntCells") {
checkIntInterpretAs(
sourceTile.convert(IntCellType),
IntUserDefinedNoDataCellType,
IntConstantNoDataCellType)
}

it("should interpretAs for ShortCells") {
checkIntInterpretAs(
sourceTile.convert(ShortCellType),
x => ShortUserDefinedNoDataCellType(x.toShort),
ShortConstantNoDataCellType)
}

it("should interpretAs for UShortCells") {
checkIntInterpretAs(
sourceTile.convert(UShortCellType),
x => UShortUserDefinedNoDataCellType(x.toShort),
UShortConstantNoDataCellType)
}

it("should interpretAs for ByteCells") {
checkIntInterpretAs(
sourceTile.convert(ByteCellType),
x => ByteUserDefinedNoDataCellType(x.toByte),
ByteConstantNoDataCellType)
}

it("should interpretAs for UByteCells") {
checkIntInterpretAs(
sourceTile.convert(UByteCellType),
x => UByteUserDefinedNoDataCellType(x.toByte),
UByteConstantNoDataCellType)
}
}
}
13 changes: 13 additions & 0 deletions raster/src/main/scala/geotrellis/raster/ArrayMultibandTile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ class ArrayMultibandTile(_bands: Array[Tile]) extends MultibandTile with MacroMu
ArrayMultibandTile(newBands)
}

/** Return tile with cellType that reflects new NoData value */
def withNoData(noDataValue: Option[Double]): MultibandTile =
new ArrayMultibandTile(_bands.map(_.withNoData(noDataValue)))

/** Changes the interpretation of the tile cells through changing NoData handling and optionally cell data type.
* If [[DataType]] portion of the [[CellType]] is unchanged the tile data is not duplicated through conversion.
* If cell [[DataType]] conversion is required it is done in a naive way, without considering NoData handling.
*
* @param newCellType CellType to be used in interpreting existing cells
*/
def interpretAs(newCellType: CellType): MultibandTile =
new ArrayMultibandTile(_bands.map(_.interpretAs(newCellType)))

/**
* Map over a subset of the bands of an [[ArrayMultibandTile]] to
* create a new integer-valued [[MultibandTile]].
Expand Down
6 changes: 5 additions & 1 deletion raster/src/main/scala/geotrellis/raster/ArrayTile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ trait ArrayTile extends Tile with Serializable {
* @param targetCellType The type of cells that the result should have
* @return The new Tile
*/
def convert(targetCellType: CellType): Tile = {
def convert(targetCellType: CellType): ArrayTile = {
val tile = ArrayTile.alloc(targetCellType, cols, rows)

if(targetCellType.isFloatingPoint != cellType.isFloatingPoint)
Expand All @@ -62,6 +62,10 @@ trait ArrayTile extends Tile with Serializable {
tile
}

def withNoData(noDataValue: Option[Double]): ArrayTile

def interpretAs(newCellType: CellType): ArrayTile

/**
* Execute a function on each cell of the [[ArrayTile]].
*
Expand Down
16 changes: 12 additions & 4 deletions raster/src/main/scala/geotrellis/raster/BitArrayTile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@

package geotrellis.raster

import geotrellis.vector.Extent

import spire.syntax.cfor._


/**
* [[ArrayTile]] based on an Array[Byte] as a bitmask; values are 0
Expand Down Expand Up @@ -133,6 +129,18 @@ final case class BitArrayTile(val array: Array[Byte], cols: Int, rows: Int)
* @return An array of bytes
*/
def toBytes: Array[Byte] = array.clone

def withNoData(noDataValue: Option[Double]): BitArrayTile =
BitArrayTile(array, cols, rows)

def interpretAs(newCellType: CellType): ArrayTile = {
newCellType match {
case dt: ByteCells with NoDataHandling =>
ByteArrayTile(array, cols, rows, dt)
case _ =>
withNoData(None).convert(newCellType)
}
}
}

/**
Expand Down
46 changes: 40 additions & 6 deletions raster/src/main/scala/geotrellis/raster/ByteArrayTile.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package geotrellis.raster

import geotrellis.vector.Extent
import geotrellis.raster.resample._

import spire.syntax.cfor._
import java.nio.ByteBuffer


/**
* [[ArrayTile]] based on Array[Byte] (each cell as a Byte).
Expand All @@ -28,6 +22,20 @@ abstract class ByteArrayTile(val array: Array[Byte], cols: Int, rows: Int)
* @return The copy
*/
def copy: ByteArrayTile = ArrayTile(array.clone, cols, rows)

def withNoData(noDataValue: Option[Double]): ByteArrayTile =
ByteArrayTile(array, cols, rows, cellType.withNoData(noDataValue))

def interpretAs(newCellType: CellType): ArrayTile = {
newCellType match {
case dt: ByteCells with NoDataHandling =>
ByteArrayTile(array, cols, rows, dt)
case dt: UByteCells with NoDataHandling =>
UByteArrayTile(array, cols, rows, dt)
case _ =>
withNoData(None).convert(newCellType)
}
}
}

/**
Expand Down Expand Up @@ -205,6 +213,32 @@ object ByteArrayTile {
new ByteUserDefinedNoDataArrayTile(arr, cols, rows, udct)
}

/**
* Create a new [[ByteArrayTile]] from an array of integers, a
* number of columns, and a number of rows.
*
* @param arr An array of bytes
* @param cols The number of columns
* @param rows The number of rows
* @param noDataValue Optional NODATA value
* @return A new ByteArrayTile
*/
def apply(arr: Array[Byte], cols: Int, rows: Int, noDataValue: Option[Byte]): ByteArrayTile =
apply(arr, cols, rows, ByteCells.withNoData(noDataValue))

/**
* Create a new [[ByteArrayTile]] from an array of bytes, a
* number of columns, and a number of rows.
*
* @param arr An array of bytes
* @param cols The number of columns
* @param rows The number of rows
* @param noDataValue NODATA value
* @return A new ByteArrayTile
*/
def apply(arr: Array[Byte], cols: Int, rows: Int, noDataValue: Byte): ByteArrayTile =
apply(arr, cols, rows, Some(noDataValue))

/**
* Produce a [[ByteArrayTile]] of the specified dimensions.
*
Expand Down
Loading