Skip to content

Instantly share code, notes, and snippets.

@lossyrob
Created November 8, 2016 23:21
Show Gist options
  • Save lossyrob/2547a2cad254e9290280368fccf225ab to your computer and use it in GitHub Desktop.
Save lossyrob/2547a2cad254e9290280368fccf225ab to your computer and use it in GitHub Desktop.
LazyConvertedTile.scala
/**
* The reason this doesn't work is that you have to pass each value through the target cell type conversions. This will have
* unique problems for each cell type. For instance, if your inner tile is a DoubleConstantNoDataCellType and has a Double.NaN
* value, and your target cell type is a ByteCellType, you have to flatten the NaN out to 0 before passing it back from
* a getDouble in order to match what a convert(ByteCellType).toArrayTile.get(0, 0) would be.
* Like wise, think of pulling out a (Byte.MinValue.toInt - 5).toInt value on a IntConstantNoDataCellType lazily converted
* to a ByteCellType vs pulled out after a toArray call.
*/
package geotrellis.raster
import geotrellis.raster.resample._
import geotrellis.vector.Extent
import spire.syntax.cfor._
object LazyConvertedTile {
def apply(inner: Tile, cellType: CellType): LazyConvertedTile =
cellType match {
case ct: DataType with ConstantNoData =>
apply(inner, ct)
case ct: DataType with NoNoData =>
apply(inner, ct)
case ct: DataType with UserDefinedNoData[_] =>
apply(inner, cellType, ct.intNoDataValue, ct.doubleNoDataValue)
}
def apply(inner: Tile, cellType: DataType with ConstantNoData)(implicit d: DummyImplicit) =
if(inner.cellType.isFloatingPoint) {
cellType match {
case UByteConstantNoDataCellType =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = ub2i(d2ub(inner.getDouble(col, row)))
def getDouble(col: Int, row: Int): Double = ub2d(d2ub(inner.getDouble(col, row)))
}
case UShortConstantNoDataCellType =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = us2i(d2us(inner.getDouble(col, row)))
def getDouble(col: Int, row: Int): Double = us2d(d2us(inner.getDouble(col, row)))
}
case _ =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = d2i(inner.getDouble(col, row))
def getDouble(col: Int, row: Int): Double = inner.getDouble(col, row)
}
}
} else {
cellType match {
case UByteConstantNoDataCellType =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = ub2i(i2ub(inner.get(col, row)))
def getDouble(col: Int, row: Int): Double = ub2d(i2ub(inner.get(col, row)))
}
case UShortConstantNoDataCellType =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = us2i(i2us(inner.get(col, row)))
def getDouble(col: Int, row: Int): Double = us2d(i2us(inner.get(col, row)))
}
case _ =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = inner.get(col, row)
def getDouble(col: Int, row: Int): Double = i2d(inner.get(col, row))
}
}
}
def apply(inner: Tile, cellType: DataType with NoNoData)(implicit d: DummyImplicit, d2: DummyImplicit) =
cellType match {
case BitCellType =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = inner.get(col, row) & 1
def getDouble(col: Int, row: Int): Double = (inner.get(col, row) & 1).toDouble
}
case UByteCellType =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = inner.get(col, row) & 0xFF
def getDouble(col: Int, row: Int): Double = (inner.get(col, row) & 0xFF).toDouble
}
case UShortCellType =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = inner.get(col, row) & 0xFFFF
def getDouble(col: Int, row: Int): Double = (inner.get(col, row) & 0xFFFF).toDouble
}
case _ if !ct.isFloatingPoint =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = inner.get(col, row)
def getDouble(col: Int, row: Int): Double = inner.getDouble(col, row)
}
case _ if ct.isFloatingPoint =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = inner.get(col, row)
def getDouble(col: Int, row: Int): Double = inner.getDouble(col, row)
}
}
def apply[@specialized(Byte, Short, Int, Float, Double) T](inner: Tile, cellType: DataType with UserDefinedNoData[T])
(implicit d: DummyImplicit, d2: DummyImplicit, d3: DummyImplicit): LazyConvertedTile =
apply(inner, cellType, cellType.intNoDataValue, cellType.doubleNoDataValue)
def apply(inner: Tile, cellType: CellType, intNoDataValue: Int, doubleNoDataValue: Double): LazyConvertedTile =
if(inner.cellType.isFloatingPoint) {
cellType match {
case ct: UByteUserDefinedNoDataCellType =>
new LazyConvertedTile(inner, cellType) with UserDefinedByteNoDataConversions {
val userDefinedByteNoDataValue: Byte = ct.noDataValue
def get(col: Int, row: Int): Int =
udub2i(d2udb(inner.getDouble(col, row)))
def getDouble(col: Int, row: Int): Double =
udub2d(d2udb(inner.getDouble(col, row)))
}
case ct: UShortUserDefinedNoDataCellType =>
new LazyConvertedTile(inner, cellType) with UserDefinedShortNoDataConversions {
val userDefinedShortNoDataValue: Short = ct.noDataValue
def get(col: Int, row: Int): Int =
udus2i(d2uds(inner.getDouble(col, row)))
def getDouble(col: Int, row: Int): Double =
udus2d(d2uds(inner.getDouble(col, row)))
}
case _ =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = {
val z = d2i(inner.getDouble(col, row))
if(z == intNoDataValue) { NODATA } else { z }
}
def getDouble(col: Int, row: Int): Double = {
val z = inner.getDouble(col, row)
if(z == doubleNoDataValue) { Double.NaN } else { z }
}
}
}
} else {
cellType match {
case UByteCellType =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = {
val z = ub2i(i2ub(inner.get(col, row)))
if(z == intNoDataValue) { NODATA } else { z }
}
def getDouble(col: Int, row: Int): Double = {
val z = ub2d(i2ub(inner.get(col, row)))
if(z == doubleNoDataValue) { Double.NaN } else { z }
}
}
case UShortCellType =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = {
val z = us2i(i2us(inner.get(col, row)))
if(z == intNoDataValue) { NODATA } else { z }
}
def getDouble(col: Int, row: Int): Double = {
val z = us2d(i2us(inner.get(col, row)))
if(z == doubleNoDataValue) { Double.NaN } else { z }
}
}
case _ =>
new LazyConvertedTile(inner, cellType) {
def get(col: Int, row: Int): Int = {
val z = inner.get(col, row)
if(z == intNoDataValue) { NODATA } else { z }
}
def getDouble(col: Int, row: Int): Double = {
val z = i2d(inner.get(col, row))
if(z == doubleNoDataValue) { Double.NaN } else { z }
}
}
}
}
}
/**
* [[LazyConvertedTile]] represents a lazily-applied conversion to
* any type.
*
* @note If you care converting to a CellType with less bits
* than the type of the underlying data, you are responsible
* for managing overflow. This convert does not do any
* casting; therefore converting from a
* IntConstantNoDataCellType to ByteConstantNoDataCellType
* could still return values greater than 127 from apply().
*/
abstract class LazyConvertedTile(inner: Tile, val cellType: CellType)
extends Tile {
val cols = inner.cols
val rows = inner.rows
/**
* Returns a [[Tile]] equivalent to this tile, except with cells of
* the given type.
*
* @param cellType The type of cells that the result should have
* @return The new Tile
*/
def convert(cellType: CellType): Tile =
LazyConvertedTile(this, cellType)
def withNoData(noDataValue: Option[Double]): Tile =
LazyConvertedTile(inner, cellType.withNoData(noDataValue))
def interpretAs(newCellType: CellType): Tile =
withNoData(None).convert(newCellType)
/**
* Return a copy of the underlying array of the present tile.
*
* @return The copy as an Array[Int]
*/
override def toArray = inner.toArray
/**
* Return a copy of the underlying array of the present tile.
*
* @return The copy as an Array[Double]
*/
override def toArrayDouble = inner.toArrayDouble
/**
* Another name for the 'mutable' method on this class.
*
* @return An [[ArrayTile]]
*/
def toArrayTile: ArrayTile = mutable
/**
* Return the [[MutableArrayTile]] equivalent of this tile.
*
* @return The MutableArrayTile
*/
def mutable: MutableArrayTile = {
val tile = ArrayTile.alloc(cellType, cols, rows)
if(!cellType.isFloatingPoint) {
inner.foreach { (col, row, z) =>
tile.set(col, row, z)
}
} else {
inner.foreachDouble { (col, row, z) =>
tile.setDouble(col, row, z)
}
}
tile
}
/**
* Return the underlying data behind this tile as an array.
*
* @return An array of bytes
*/
def toBytes(): Array[Byte] = toArrayTile.toBytes
/**
* Execute a function on each cell of the tile. The function
* returns Unit, so it presumably produces side-effects.
*
* @param f A function from Int to Unit
*/
def foreach(f: Int => Unit): Unit = inner.foreach(f)
/**
* Execute a function on each cell of the tile. The function
* returns Unit, so it presumably produces side-effects.
*
* @param f A function from Double to Unit
*/
def foreachDouble(f: Double => Unit): Unit = inner.foreachDouble(f)
/**
* Execute an [[IntTileVisitor]] at each cell of the present tile.
*
* @param visitor An IntTileVisitor
*/
def foreachIntVisitor(visitor: IntTileVisitor): Unit = inner.foreachIntVisitor(visitor)
/**
* Execute an [[DoubleTileVisitor]] at each cell of the present tile.
*
* @param visitor An DoubleTileVisitor
*/
def foreachDoubleVisitor(visitor: DoubleTileVisitor): Unit = inner.foreachDoubleVisitor(visitor)
/**
* Map each cell in the given tile to a new one, using the given
* function.
*
* @param f A function from Int to Int, executed at each point of the tile
* @return The result, a [[Tile]]
*/
def map(f: Int => Int): Tile = {
val tile = ArrayTile.alloc(cellType, cols, rows)
inner.foreach { (col, row, z) =>
tile.set(col, row, f(z))
}
tile
}
/**
* Map each cell in the given tile to a new one, using the given
* function.
*
* @param f A function from Double to Double, executed at each point of the tile
* @return The result, a [[Tile]]
*/
def mapDouble(f: Double =>Double): Tile = {
val tile = ArrayTile.alloc(cellType, cols, rows)
inner.foreachDouble { (col, row, z) =>
tile.setDouble(col, row, f(z))
}
tile
}
/**
* Map an [[IntTileMapper]] over the present tile.
*
* @param mapper The mapper
* @return The result, a [[Tile]]
*/
def mapIntMapper(mapper: IntTileMapper): Tile = {
val tile = ArrayTile.alloc(cellType, cols, rows)
inner.foreach { (col, row, z) =>
tile.set(col, row, mapper(col, row, z))
}
tile
}
/**
* Map an [[DoubleTileMapper]] over the present tile.
*
* @param mapper The mapper
* @return The result, a [[Tile]]
*/
def mapDoubleMapper(mapper: DoubleTileMapper): Tile = {
val tile = ArrayTile.alloc(cellType, cols, rows)
inner.foreachDouble { (col, row, z) =>
tile.setDouble(col, row, mapper(col, row, z))
}
tile
}
/**
* Combine two tiles' cells into new cells using the given integer
* function. For every (x, y) cell coordinate, get each of the
* tiles' integer values, map them to a new value, and assign it to
* the output's (x, y) cell.
*
* @param other The other Tile
* @param f A function from (Int, Int) to Int
* @return The result, an Tile
*/
def combine(other: Tile)(f: (Int, Int) => Int): Tile = {
(this, other).assertEqualDimensions
val tile = ArrayTile.alloc(cellType, cols, rows)
inner.foreach { (col, row, z) =>
tile.set(col, row, f(z, other.get(col, row)))
}
tile
}
/**
* Combine two tiles' cells into new cells using the given double
* function. For every (x, y) cell coordinate, get each of the
* tiles' double values, map them to a new value, and assign it to
* the output's (x, y) cell.
*
* @param other The other Tile
* @param f A function from (Int, Int) to Int
* @return The result, an Tile
*/
def combineDouble(other: Tile)(f: (Double, Double) => Double): Tile = {
(this, other).assertEqualDimensions
val tile = ArrayTile.alloc(cellType, cols, rows)
inner.foreachDouble { (col, row, z) =>
tile.setDouble(col, row, f(z, other.getDouble(col, row)))
}
tile
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment