Created
November 8, 2016 23:21
-
-
Save lossyrob/2547a2cad254e9290280368fccf225ab to your computer and use it in GitHub Desktop.
LazyConvertedTile.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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