diff --git a/project/Version.scala b/project/Version.scala index f69dd9b8a5..75bf2dae19 100644 --- a/project/Version.scala +++ b/project/Version.scala @@ -15,7 +15,7 @@ */ object Version { - val geotrellis = "0.10.2" + val geotrellis = "0.10.3" /* Even though we support cross-build to 2.11 the default target is scala 2.10 primarily because Cloudera * (and likely others) spark distributions target 2.10 in their default spark-assembly.jar. * One can envoke the cross-build to 2.11 by prefixing command with '+' (ex: + assembly) diff --git a/raster-test/src/test/scala/geotrellis/raster/io/geotiff/GeoTiffMultibandTileSpec.scala b/raster-test/src/test/scala/geotrellis/raster/io/geotiff/GeoTiffMultibandTileSpec.scala index ceec7de3a4..b9163214d5 100644 --- a/raster-test/src/test/scala/geotrellis/raster/io/geotiff/GeoTiffMultibandTileSpec.scala +++ b/raster-test/src/test/scala/geotrellis/raster/io/geotiff/GeoTiffMultibandTileSpec.scala @@ -453,6 +453,144 @@ class GeoTiffMultibandTileSpec extends FunSpec } } + describe("GeoTiffMultibandTile multiband foreach") { + it("should multiband foreach all values, striped, pixel interleave") { + + val tile = + MultibandGeoTiff(geoTiffPath("3bands/int32/3bands-striped-pixel.tif")).tile + + val bandCount = tile.bandCount + val cellCount = tile.rows * tile.cols + + var counts = 0 + tile.foreach { value => + value.length should be(bandCount) + counts += 1 + } + + counts should be(cellCount) + } + + it("should multiband foreach all values, tiled, pixel interleave") { + + val tile = + MultibandGeoTiff(geoTiffPath("3bands/int32/3bands-tiled-pixel.tif")).tile + + val bandCount = tile.bandCount + val cellCount = tile.rows * tile.cols + + var counts = 0 + tile.foreach { value => + value.length should be(bandCount) + counts += 1 + } + + counts should be(cellCount) + } + + it("should multiband foreach all values, striped, band interleave") { + + val tile = + MultibandGeoTiff(geoTiffPath("3bands/int32/3bands-striped-band.tif")).tile + + val bandCount = tile.bandCount + val cellCount = tile.rows * tile.cols + + var counts = 0 + tile.foreach { value => + value.length should be(bandCount) + counts += 1 + } + + counts should be(cellCount) + } + + it("should multiband foreach all values, tiled, band interleave") { + + val tile = + MultibandGeoTiff(geoTiffPath("3bands/int32/3bands-tiled-band.tif")).tile + + val bandCount = tile.bandCount + val cellCount = tile.rows * tile.cols + + var counts = 0 + tile.foreach { value => + value.length should be(bandCount) + counts += 1 + } + + counts should be(cellCount) + } + + it("should multiband foreachDouble all values, striped, pixel interleave") { + + val tile = + MultibandGeoTiff(geoTiffPath("3bands/int32/3bands-striped-pixel.tif")).tile + + val bandCount = tile.bandCount + val cellCount = tile.rows * tile.cols + + var counts = 0 + tile.foreachDouble { value => + value.length should be(bandCount) + counts += 1 + } + + counts should be(cellCount) + } + + it("should multiband foreachDouble all values, tiled, pixel interleave") { + + val tile = + MultibandGeoTiff(geoTiffPath("3bands/int32/3bands-tiled-pixel.tif")).tile + + val bandCount = tile.bandCount + val cellCount = tile.rows * tile.cols + + var counts = 0 + tile.foreachDouble { value => + value.length should be(bandCount) + counts += 1 + } + + counts should be(cellCount) + } + + it("should multiband foreachDouble all values, striped, band interleave") { + + val tile = + MultibandGeoTiff(geoTiffPath("3bands/int32/3bands-striped-band.tif")).tile + + val bandCount = tile.bandCount + val cellCount = tile.rows * tile.cols + + var counts = 0 + tile.foreachDouble { value => + value.length should be(bandCount) + counts += 1 + } + + counts should be(cellCount) + } + + it("should multiband foreachDouble all values, tiled, band interleave") { + + val tile = + MultibandGeoTiff(geoTiffPath("3bands/int32/3bands-tiled-band.tif")).tile + + val bandCount = tile.bandCount + val cellCount = tile.rows * tile.cols + + var counts = 0 + tile.foreachDouble { value => + value.length should be(bandCount) + counts += 1 + } + + counts should be(cellCount) + } + } + describe("GeoTiffMultibandTile combine") { val original = IntConstantTile(199, 4, 4) diff --git a/raster/src/main/scala/geotrellis/raster/ArrayMultibandTile.scala b/raster/src/main/scala/geotrellis/raster/ArrayMultibandTile.scala index 2367c0d8b9..fbe91f1994 100644 --- a/raster/src/main/scala/geotrellis/raster/ArrayMultibandTile.scala +++ b/raster/src/main/scala/geotrellis/raster/ArrayMultibandTile.scala @@ -276,6 +276,34 @@ class ArrayMultibandTile(_bands: Array[Tile]) extends MultibandTile with MacroMu band(b0) foreachDouble f } + def foreach(f: Array[Int] => Unit): Unit = { + var i = 0 + cfor(0)(_ < cols, _ + 1) { col => + cfor(0)(_ < rows, _ + 1) { row => + val bandValues = Array.ofDim[Int](bandCount) + cfor(0)(_ < bandCount, _ + 1) { band => + bandValues(band) = bands(band).get(col, row) + } + f(bandValues) + i += 1 + } + } + } + + def foreachDouble(f: Array[Double] => Unit): Unit = { + var i = 0 + cfor(0)(_ < cols, _ + 1) { col => + cfor(0)(_ < rows, _ + 1) { row => + val bandValues = Array.ofDim[Double](bandCount) + cfor(0)(_ < bandCount, _ + 1) { band => + bandValues(band) = bands(band).getDouble(col, row) + } + f(bandValues) + i += 1 + } + } + } + /** * Combine a subset of the bands of a [[ArrayMultibandTile]] into a * new integer-valued [[MultibandTile]] using the function f. diff --git a/raster/src/main/scala/geotrellis/raster/MultibandTile.scala b/raster/src/main/scala/geotrellis/raster/MultibandTile.scala index 992288e698..3f7c07615f 100644 --- a/raster/src/main/scala/geotrellis/raster/MultibandTile.scala +++ b/raster/src/main/scala/geotrellis/raster/MultibandTile.scala @@ -71,6 +71,21 @@ trait MultibandTile extends CellGrid with MacroCombinableMultibandTile[Tile] wit */ def convert(newCellType: CellType): MultibandTile + /** + * Map over each band, and return a new MultibandTile. + * + * @param f A function to apply to each band, given it's band index. + * + * @return An ArrayMultibandTile with the resulting tiles. + */ + def mapBands(f: (Int, Tile) => Tile): MultibandTile = { + val resultBands = Array.ofDim[Tile](bandCount) + cfor(0)(_ < bandCount, _ + 1) { b => + resultBands(b) = f(b, band(b)) + } + ArrayMultibandTile(resultBands) + } + /** * Map over a subset of the bands of a multiband tile to create a * new integer-valued multiband tile. @@ -164,6 +179,24 @@ trait MultibandTile extends CellGrid with MacroCombinableMultibandTile[Tile] wit */ def foreachDouble(b0: Int)(f: Double => Unit): Unit + /** + * Multiband iterate over tile's int value using a function that + * takes in an array of values, and returns the foreached + * value for that cell value. + * + * @param f The function + */ + def foreach(f: Array[Int] => Unit): Unit + + /** + * Multiband iterate over tile's double value using a function that + * takes in an array of values, and returns the foreached + * value for that cell value. + * + * @param f The function + */ + def foreachDouble(f: Array[Double] => Unit): Unit + /** * Combine a subset of the bands of a tile into a new * integer-valued multiband tile using the function f. diff --git a/raster/src/main/scala/geotrellis/raster/io/geotiff/GeoTiffMultibandTile.scala b/raster/src/main/scala/geotrellis/raster/io/geotiff/GeoTiffMultibandTile.scala index 5b60835783..a1d16c4517 100644 --- a/raster/src/main/scala/geotrellis/raster/io/geotiff/GeoTiffMultibandTile.scala +++ b/raster/src/main/scala/geotrellis/raster/io/geotiff/GeoTiffMultibandTile.scala @@ -502,6 +502,34 @@ abstract class GeoTiffMultibandTile( } } + def foreach(f: Array[Int] => Unit): Unit = { + var i = 0 + cfor(0)(_ < cols, _ + 1) { col => + cfor(0)(_ < rows, _ + 1) { row => + val bandValues = Array.ofDim[Int](bandCount) + cfor(0)(_ < bandCount, _ + 1) { band => + bandValues(band) = bands(band).get(col, row) + } + f(bandValues) + i += 1 + } + } + } + + def foreachDouble(f: Array[Double] => Unit): Unit = { + var i = 0 + cfor(0)(_ < cols, _ + 1) { col => + cfor(0)(_ < rows, _ + 1) { row => + val bandValues = Array.ofDim[Double](bandCount) + cfor(0)(_ < bandCount, _ + 1) { band => + bandValues(band) = bands(band).getDouble(col, row) + } + f(bandValues) + i += 1 + } + } + } + /** * Piggy-back on the other combine method to support combing a * subset of the bands. diff --git a/spark/src/main/scala/geotrellis/spark/summary/polygonal/Implicits.scala b/spark/src/main/scala/geotrellis/spark/summary/polygonal/Implicits.scala index a55752b2d9..9e315a6a26 100644 --- a/spark/src/main/scala/geotrellis/spark/summary/polygonal/Implicits.scala +++ b/spark/src/main/scala/geotrellis/spark/summary/polygonal/Implicits.scala @@ -1,16 +1,23 @@ package geotrellis.spark.summary.polygonal +import geotrellis.raster._ import geotrellis.spark._ +import geotrellis.spark.tiling._ import geotrellis.vector._ +import geotrellis.util._ + import org.apache.spark.rdd._ import scala.reflect._ object Implicits extends Implicits trait Implicits { - implicit class withZonalSummaryTileLayerRDDMethods[K](val self: TileLayerRDD[K]) + implicit class withZonalSummaryTileLayerRDDMethods[ + K, + M: GetComponent[?, LayoutDefinition] + ](val self: RDD[(K, Tile)] with Metadata[M]) (implicit val keyClassTag: ClassTag[K], implicit val _sc: SpatialComponent[K]) - extends PolygonalSummaryTileLayerRDDMethods[K] with Serializable + extends PolygonalSummaryTileLayerRDDMethods[K, M] with Serializable implicit class withZonalSummaryFeatureRDDMethods[G <: Geometry, D](val featureRdd: RDD[Feature[G, D]]) extends PolygonalSummaryFeatureRDDMethods[G, D] @@ -19,4 +26,4 @@ trait Implicits { (implicit val keyClassTag: ClassTag[K]) extends PolygonalSummaryKeyedFeatureRDDMethods[K, G, D] -} \ No newline at end of file +} diff --git a/spark/src/main/scala/geotrellis/spark/summary/polygonal/PolygonalSummaryTileRDDMethods.scala b/spark/src/main/scala/geotrellis/spark/summary/polygonal/PolygonalSummaryTileRDDMethods.scala index 2e4120b3ce..764a2fb453 100644 --- a/spark/src/main/scala/geotrellis/spark/summary/polygonal/PolygonalSummaryTileRDDMethods.scala +++ b/spark/src/main/scala/geotrellis/spark/summary/polygonal/PolygonalSummaryTileRDDMethods.scala @@ -4,6 +4,7 @@ import geotrellis.raster.summary.polygonal._ import geotrellis.raster.histogram._ import geotrellis.raster._ import geotrellis.spark._ +import geotrellis.spark.tiling._ import geotrellis.vector._ import geotrellis.util._ @@ -12,7 +13,10 @@ import org.apache.spark.rdd._ import scala.reflect.ClassTag -abstract class PolygonalSummaryTileLayerRDDMethods[K: ClassTag] extends MethodExtensions[TileLayerRDD[K]] { +abstract class PolygonalSummaryTileLayerRDDMethods[ + K: ClassTag, + M: GetComponent[?, LayoutDefinition] +] extends MethodExtensions[RDD[(K, Tile)] with Metadata[M]] { import Implicits._ protected implicit val _sc: SpatialComponent[K] @@ -37,17 +41,20 @@ abstract class PolygonalSummaryTileLayerRDDMethods[K: ClassTag] extends MethodEx .polygonalSummary(multiPolygon, zeroValue)(handler) def polygonalSummaryByKey[T: ClassTag, L: ClassTag]( - polygon: Polygon, - zeroValue: T, - handler: TilePolygonalSummaryHandler[T], - fKey: K => L): RDD[(L, T)] = polygonalSummaryByKey(polygon, zeroValue, handler, fKey, None) + polygon: Polygon, + zeroValue: T, + handler: TilePolygonalSummaryHandler[T], + fKey: K => L + ): RDD[(L, T)] = { + polygonalSummaryByKey(polygon, zeroValue, handler, fKey, None) + } def polygonalSummaryByKey[T: ClassTag, L: ClassTag]( - polygon: Polygon, - zeroValue: T, - handler: TilePolygonalSummaryHandler[T], - fKey: K => L, - partitioner: Option[Partitioner] + polygon: Polygon, + zeroValue: T, + handler: TilePolygonalSummaryHandler[T], + fKey: K => L, + partitioner: Option[Partitioner] ): RDD[(L, T)] = self .asRasters @@ -55,17 +62,20 @@ abstract class PolygonalSummaryTileLayerRDDMethods[K: ClassTag] extends MethodEx .polygonalSummaryByKey(polygon, zeroValue, partitioner)(handler) def polygonalSummaryByKey[T: ClassTag, L: ClassTag]( - multiPolygon: MultiPolygon, - zeroValue: T, - handler: TilePolygonalSummaryHandler[T], - fKey: K => L): RDD[(L, T)] = polygonalSummaryByKey(multiPolygon, zeroValue, handler, fKey, None) + multiPolygon: MultiPolygon, + zeroValue: T, + handler: TilePolygonalSummaryHandler[T], + fKey: K => L + ): RDD[(L, T)] = { + polygonalSummaryByKey(multiPolygon, zeroValue, handler, fKey, None) + } def polygonalSummaryByKey[T: ClassTag, L: ClassTag]( - multiPolygon: MultiPolygon, - zeroValue: T, - handler: TilePolygonalSummaryHandler[T], - fKey: K => L, - partitioner: Option[Partitioner] + multiPolygon: MultiPolygon, + zeroValue: T, + handler: TilePolygonalSummaryHandler[T], + fKey: K => L, + partitioner: Option[Partitioner] ): RDD[(L, T)] = self .asRasters