@@ -29,7 +29,7 @@ import geotrellis.raster
2929import geotrellis .raster .testkit .RasterMatchers
3030import geotrellis .raster .{BitCellType , ByteUserDefinedNoDataCellType , DoubleConstantNoDataCellType , ShortConstantNoDataCellType , Tile , UByteConstantNoDataCellType }
3131import geotrellis .vector .Extent
32- import org .apache .spark .sql .Encoders
32+ import org .apache .spark .sql .{ AnalysisException , Encoders }
3333import org .apache .spark .sql .functions ._
3434import org .scalatest .{FunSpec , Matchers }
3535
@@ -45,6 +45,7 @@ class RasterFunctionsSpec extends FunSpec
4545 val tileSize = cols * rows
4646 val tileCount = 10
4747 val numND = 4
48+ lazy val zero = TestData .projectedRasterTile(cols, rows, 0 , extent, crs, ct)
4849 lazy val one = TestData .projectedRasterTile(cols, rows, 1 , extent, crs, ct)
4950 lazy val two = TestData .projectedRasterTile(cols, rows, 2 , extent, crs, ct)
5051 lazy val three = TestData .projectedRasterTile(cols, rows, 3 , extent, crs, ct)
@@ -55,6 +56,7 @@ class RasterFunctionsSpec extends FunSpec
5556
5657 lazy val randDoubleTile = TestData .projectedRasterTile(cols, rows, scala.util.Random .nextGaussian(), extent, crs, DoubleConstantNoDataCellType )
5758 lazy val randDoubleNDTile = TestData .injectND(numND)(randDoubleTile)
59+ lazy val randPositiveDoubleTile = TestData .projectedRasterTile(cols, rows, scala.util.Random .nextDouble() + 1e-6 , extent, crs, DoubleConstantNoDataCellType )
5860
5961 val expectedRandNoData : Long = numND * tileCount
6062 val expectedRandData : Long = cols * rows * tileCount - expectedRandNoData
@@ -113,6 +115,9 @@ class RasterFunctionsSpec extends FunSpec
113115
114116 assertEqual(df.selectExpr(" rf_local_divide(six, two)" ).as[ProjectedRasterTile ].first(), three)
115117
118+ assertEqual(df.selectExpr(" rf_local_multiply(rf_local_divide(six, 2.0), two)" )
119+ .as[ProjectedRasterTile ].first(), six)
120+
116121 val maybeThreeTile =
117122 df.select(local_divide(ExtractTile ($" six" ), ExtractTile ($" two" ))).as[Tile ]
118123 assertEqual(maybeThreeTile.first(), three.toArrayTile())
@@ -540,40 +545,67 @@ class RasterFunctionsSpec extends FunSpec
540545
541546 val df = Seq ((three_plus, three_less, three)).toDF(" three_plus" , " three_less" , " three" )
542547
543- assertEqual(df.select(round($" three_plus " )).as[Tile ].first(), three_double )
544- assertEqual(df.select(round($" three_less " )).as[Tile ].first(), three_double)
545- assertEqual(df.select(round($" three " )).as[Tile ].first(), three )
548+ assertEqual(df.select(round($" three " )).as[ProjectedRasterTile ].first(), three )
549+ assertEqual(df.select(round($" three_plus " )).as[ProjectedRasterTile ].first(), three_double)
550+ assertEqual(df.select(round($" three_less " )).as[ProjectedRasterTile ].first(), three_double )
546551
552+ assertEqual(df.selectExpr(" rf_round(three)" ).as[ProjectedRasterTile ].first(), three)
547553 assertEqual(df.selectExpr(" rf_round(three_plus)" ).as[ProjectedRasterTile ].first(), three_double)
548554 assertEqual(df.selectExpr(" rf_round(three_less)" ).as[ProjectedRasterTile ].first(), three_double)
549- assertEqual(df.selectExpr(" rf_round(three)" ).as[ProjectedRasterTile ].first(), three)
550555
551556 checkDocs(" rf_round" )
552557 }
553558
554- it(" should take logarithms" ){
555- // tile zeros ==> nodata
556- val zeros = TestData .projectedRasterTile(cols, rows, 0 , extent, crs, ct)
557- val nd_float = TestData .projectedRasterTile(cols, rows, Double .NaN , extent, crs, DoubleConstantNoDataCellType )
558- val df_0 = Seq (zeros).toDF(" tile" )
559- assertEqual(df_0.select(log($" tile" )).as[Tile ].first(), nd_float)
560-
559+ it(" should take logarithms positive cell values" ){
561560 // log10 1000 == 3
562- val one_k = TestData .projectedRasterTile(cols, rows, 1000 , extent, crs, ShortConstantNoDataCellType )
563- val threes_dbl = TestData .projectedRasterTile(cols, rows, 3.0 , extent, crs, DoubleConstantNoDataCellType )
561+ val thousand = TestData .projectedRasterTile(cols, rows, 1000 , extent, crs, ShortConstantNoDataCellType )
562+ val threesDouble = TestData .projectedRasterTile(cols, rows, 3.0 , extent, crs, DoubleConstantNoDataCellType )
563+ val zerosDouble = TestData .projectedRasterTile(cols, rows, 0.0 , extent, crs, DoubleConstantNoDataCellType )
564564
565- val df_1 = Seq (one_k ).toDF(" tile" )
566- assertEqual(df_1 .select(log10($" tile" )).as[Tile ].first(), threes_dbl )
565+ val df1 = Seq (thousand ).toDF(" tile" )
566+ assertEqual(df1 .select(log10($" tile" )).as[ProjectedRasterTile ].first(), threesDouble )
567567
568- // ln random tile == log10 random tile / log10(e)
569- val df_2 = Seq (randDoubleTile ).toDF(" tile" )
568+ // ln random tile == log10 random tile / log10(e); random tile square to ensure all positive cell values
569+ val df2 = Seq (randPositiveDoubleTile ).toDF(" tile" )
570570 val log10e = math.log10(math.E )
571- assertEqual(df_2.select(log($" tile" )).as[Tile ].first(), df_2.select(log10($" tile" )).as[Tile ].first / log10e)
571+ assertEqual(df2.select(log($" tile" )).as[ProjectedRasterTile ].first(),
572+ df2.select(log10($" tile" )).as[ProjectedRasterTile ].first() / log10e)
573+
574+ lazy val maybeZeros = df2
575+ .selectExpr(s " rf_local_subtract(rf_log(tile), rf_local_divide(rf_log10(tile), ${log10e})) " )
576+ .as[ProjectedRasterTile ].first()
577+ assertEqual(maybeZeros, zerosDouble)
572578
573- val maybe_all = df_2.selectExpr(s " rf_local_equal(rf_log(tile), rf_local_divide(rf_log10(tile), ${log10e}) " ).as[Tile ].first()
574- assertEqual(maybe_all, TestData .projectedRasterTile(cols, rows, 1 , extent, crs, BitCellType ))
579+ // log1p for zeros should be ln(1)
580+ val ln1 = math.log1p(0.0 )
581+ val df3 = Seq (zero).toDF(" tile" )
582+ val maybeLn1 = df3.selectExpr(s " rf_log1p(tile) " ).as[ProjectedRasterTile ].first()
583+ assert(maybeLn1.toArrayDouble().forall(_ == ln1))
575584
576585 checkDocs(" rf_log" )
586+ checkDocs(" rf_log2" )
587+ checkDocs(" rf_log10" )
588+ checkDocs(" rf_log1p" )
589+ }
590+
591+ it(" should take logarithms with non-positive cell values" ) {
592+ val ni_float = TestData .projectedRasterTile(cols, rows, Double .NegativeInfinity , extent, crs, DoubleConstantNoDataCellType )
593+ val zero_float = TestData .projectedRasterTile(cols, rows, 0.0 , extent, crs, DoubleConstantNoDataCellType )
594+
595+ // tile zeros ==> -Infinity
596+ val df_0 = Seq (zero).toDF(" tile" )
597+ assertEqual(df_0.select(log($" tile" )).as[ProjectedRasterTile ].first(), ni_float)
598+ assertEqual(df_0.select(log10($" tile" )).as[ProjectedRasterTile ].first(), ni_float)
599+ assertEqual(df_0.select(log2($" tile" )).as[ProjectedRasterTile ].first(), ni_float)
600+ // log1p of zeros should be 0.
601+ assertEqual(df_0.select(log1p($" tile" )).as[ProjectedRasterTile ].first(), zero_float)
602+
603+ // tile negative values ==> NaN
604+ assert(df_0.selectExpr(" rf_log(rf_local_subtract(tile, 42))" ).as[ProjectedRasterTile ].first().isNoDataTile)
605+ assert(df_0.selectExpr(" rf_log2(rf_local_subtract(tile, 42))" ).as[ProjectedRasterTile ].first().isNoDataTile)
606+ assert(df_0.select(log1p(local_subtract($" tile" , 42 ))).as[ProjectedRasterTile ].first().isNoDataTile)
607+ assert(df_0.select(log10(local_subtract($" tile" , lit(0.01 )))).as[ProjectedRasterTile ].first().isNoDataTile)
608+
577609 }
578610 }
579611}
0 commit comments