Skip to content

Commit 88ba7df

Browse files
committed
Merge remote-tracking branch 'locationtech/develop' into feature/tile_interpret_as
Signed-off-by: Jason T. Brown <jason@astraea.earth>
2 parents 414b53b + dc050ad commit 88ba7df

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+916
-612
lines changed

build.sbt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,20 +129,28 @@ lazy val experimental = project
129129

130130
lazy val docs = project
131131
.dependsOn(core, datasource, pyrasterframes)
132-
.enablePlugins(SiteScaladocPlugin, ParadoxPlugin, GhpagesPlugin, ScalaUnidocPlugin)
132+
.enablePlugins(SiteScaladocPlugin, ParadoxPlugin, ParadoxMaterialThemePlugin, GhpagesPlugin, ScalaUnidocPlugin)
133133
.settings(
134134
apiURL := Some(url("http://rasterframes.io/latest/api")),
135135
autoAPIMappings := true,
136136
ghpagesNoJekyll := true,
137137
ScalaUnidoc / siteSubdirName := "latest/api",
138138
paradox / siteSubdirName := ".",
139139
paradoxProperties ++= Map(
140-
"github.base_url" -> "https://github.com/locationtech/rasterframes",
141140
"version" -> version.value,
142-
"scaladoc.org.apache.spark.sql.rf" -> "http://rasterframes.io/latest"
141+
"scaladoc.org.apache.spark.sql.rf" -> "http://rasterframes.io/latest",
142+
"github.base_url" -> ""
143143
),
144144
paradoxNavigationExpandDepth := Some(3),
145-
paradoxTheme := Some(builtinParadoxTheme("generic")),
145+
Compile / paradoxMaterialTheme ~= { _
146+
.withRepository(uri("https://github.com/locationtech/rasterframes"))
147+
.withCustomStylesheet("assets/custom.css")
148+
.withCopyright("""&copy; 2017-2019 <a href="https://astraea.earth">Astraea</a>, Inc. All rights reserved.""")
149+
.withLogo("assets/images/RF-R.svg")
150+
.withFavicon("assets/images/RasterFrames_32x32.ico")
151+
.withColor("blue-grey", "light-blue")
152+
.withGoogleAnalytics("UA-106630615-1")
153+
},
146154
makeSite := makeSite
147155
.dependsOn(Compile / unidoc)
148156
.dependsOn((Compile / paradox)
@@ -157,6 +165,8 @@ lazy val docs = project
157165
addMappingsToSiteDir(Compile / paradox / mappings, paradox / siteSubdirName)
158166
)
159167

168+
//ParadoxMaterialThemePlugin.paradoxMaterialThemeSettings(Paradox)
169+
160170
lazy val bench = project
161171
.dependsOn(core % "compile->test")
162172
.settings(publish / skip := true)

core/src/main/scala/org/locationtech/rasterframes/RasterFunctions.scala

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
package org.locationtech.rasterframes
2323
import geotrellis.proj4.CRS
2424
import geotrellis.raster.mapalgebra.local.LocalTileBinaryOp
25+
import geotrellis.raster.render.ColorRamp
2526
import geotrellis.raster.{CellType, Tile}
2627
import geotrellis.vector.Extent
2728
import org.apache.spark.annotation.Experimental
@@ -34,6 +35,7 @@ import org.locationtech.rasterframes.expressions.aggregates._
3435
import org.locationtech.rasterframes.expressions.generators._
3536
import org.locationtech.rasterframes.expressions.localops._
3637
import org.locationtech.rasterframes.expressions.tilestats._
38+
import org.locationtech.rasterframes.expressions.transformers.RenderPNG.{RenderCompositePNG, RenderColorRampPNG}
3739
import org.locationtech.rasterframes.expressions.transformers._
3840
import org.locationtech.rasterframes.model.TileDimensions
3941
import org.locationtech.rasterframes.stats._
@@ -81,6 +83,10 @@ trait RasterFunctions {
8183
def rf_assemble_tile(columnIndex: Column, rowIndex: Column, cellData: Column, tileCols: Int, tileRows: Int, ct: CellType): TypedColumn[Any, Tile] =
8284
rf_convert_cell_type(TileAssembler(columnIndex, rowIndex, cellData, lit(tileCols), lit(tileRows)), ct).as(cellData.columnName).as[Tile](singlebandTileEncoder)
8385

86+
/** Create a Tile from a column of cell data with location indexes and perform cell conversion. */
87+
def rf_assemble_tile(columnIndex: Column, rowIndex: Column, cellData: Column, tileCols: Int, tileRows: Int): TypedColumn[Any, Tile] =
88+
TileAssembler(columnIndex, rowIndex, cellData, lit(tileCols), lit(tileRows))
89+
8490
/** Create a Tile from a column of cell data with location indexes. */
8591
def rf_assemble_tile(columnIndex: Column, rowIndex: Column, cellData: Column, tileCols: Column, tileRows: Column): TypedColumn[Any, Tile] =
8692
TileAssembler(columnIndex, rowIndex, cellData, tileCols, tileRows)
@@ -329,12 +335,24 @@ trait RasterFunctions {
329335
ReprojectGeometry(sourceGeom, srcCRSCol, dstCRSCol)
330336

331337
/** Render Tile as ASCII string, for debugging purposes. */
332-
def rf_render_ascii(col: Column): TypedColumn[Any, String] =
333-
DebugRender.RenderAscii(col)
338+
def rf_render_ascii(tile: Column): TypedColumn[Any, String] =
339+
DebugRender.RenderAscii(tile)
334340

335341
/** Render Tile cell values as numeric values, for debugging purposes. */
336-
def rf_render_matrix(col: Column): TypedColumn[Any, String] =
337-
DebugRender.RenderMatrix(col)
342+
def rf_render_matrix(tile: Column): TypedColumn[Any, String] =
343+
DebugRender.RenderMatrix(tile)
344+
345+
/** Converts tiles in a column into PNG encoded byte array, using given ColorRamp to assign values to colors. */
346+
def rf_render_png(tile: Column, colors: ColorRamp): TypedColumn[Any, Array[Byte]] =
347+
RenderColorRampPNG(tile, colors)
348+
349+
/** Converts columns of tiles representing RGB channels into a PNG encoded byte array. */
350+
def rf_render_png(red: Column, green: Column, blue: Column): TypedColumn[Any, Array[Byte]] =
351+
RenderCompositePNG(red, green, blue)
352+
353+
/** Converts columns of tiles representing RGB channels into a single RGB packaged tile. */
354+
def rf_rgb_composite(red: Column, green: Column, blue: Column): Column =
355+
RGBComposite(red, green, blue)
338356

339357
/** Cellwise less than value comparison between two tiles. */
340358
def rf_local_less(left: Column, right: Column): Column = Less(left, right)

core/src/main/scala/org/locationtech/rasterframes/expressions/localops/Defined.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import org.locationtech.rasterframes.expressions.{NullToValue, UnaryLocalRasterO
4040
case class Defined(child: Expression) extends UnaryLocalRasterOp
4141
with NullToValue with CodegenFallback {
4242
override def nodeName: String = "rf_local_data"
43-
override def na: Any = null
43+
override def na: Any = nulter
4444
override protected def op(child: Tile): Tile = child.localDefined()
4545
}
4646
object Defined{

core/src/main/scala/org/locationtech/rasterframes/expressions/package.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ package object expressions {
131131

132132
registry.registerExpression[DebugRender.RenderAscii]("rf_render_ascii")
133133
registry.registerExpression[DebugRender.RenderMatrix]("rf_render_matrix")
134+
registry.registerExpression[RenderPNG.RenderCompositePNG]("rf_render_png")
135+
registry.registerExpression[RGBComposite]("rf_rgb_composite")
136+
134137
registry.registerExpression[transformers.ReprojectGeometry]("st_reproject")
135138
}
136139
}

core/src/main/scala/org/locationtech/rasterframes/expressions/transformers/DebugRender.scala

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,29 @@
2121

2222
package org.locationtech.rasterframes.expressions.transformers
2323

24-
import org.locationtech.rasterframes.expressions.UnaryRasterOp
25-
import org.locationtech.rasterframes.util.TileAsMatrix
26-
import geotrellis.raster.Tile
2724
import geotrellis.raster.render.ascii.AsciiArtEncoder
25+
import geotrellis.raster.{Tile, isNoData}
2826
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
2927
import org.apache.spark.sql.catalyst.expressions.{Expression, ExpressionDescription}
3028
import org.apache.spark.sql.types.{DataType, StringType}
3129
import org.apache.spark.sql.{Column, TypedColumn}
3230
import org.apache.spark.unsafe.types.UTF8String
31+
import org.locationtech.rasterframes.expressions.UnaryRasterOp
3332
import org.locationtech.rasterframes.model.TileContext
33+
import spire.syntax.cfor.cfor
3434

3535
abstract class DebugRender(asciiArt: Boolean) extends UnaryRasterOp
36-
with CodegenFallback with Serializable {
37-
override def dataType: DataType = StringType
36+
with CodegenFallback with Serializable {
37+
import org.locationtech.rasterframes.expressions.transformers.DebugRender.TileAsMatrix
38+
override def dataType: DataType = StringType
3839

39-
override protected def eval(tile: Tile, ctx: Option[TileContext]): Any = {
40-
UTF8String.fromString(if (asciiArt)
41-
s"\n${tile.renderAscii(AsciiArtEncoder.Palette.NARROW)}\n"
42-
else
43-
s"\n${tile.renderMatrix(6)}\n"
44-
)
45-
}
40+
override protected def eval(tile: Tile, ctx: Option[TileContext]): Any = {
41+
UTF8String.fromString(if (asciiArt)
42+
s"\n${tile.renderAscii(AsciiArtEncoder.Palette.NARROW)}\n"
43+
else
44+
s"\n${tile.renderMatrix(6)}\n"
45+
)
46+
}
4647
}
4748

4849
object DebugRender {
@@ -75,4 +76,29 @@ object DebugRender {
7576
def apply(tile: Column): TypedColumn[Any, String] =
7677
new Column(RenderMatrix(tile.expr)).as[String]
7778
}
79+
80+
implicit class TileAsMatrix(val tile: Tile) extends AnyVal {
81+
def renderMatrix(significantDigits: Int): String = {
82+
val ND = s"%${significantDigits+5}s".format(Double.NaN)
83+
val fmt = s"% ${significantDigits+5}.${significantDigits}g"
84+
val buf = new StringBuilder("[")
85+
cfor(0)(_ < tile.rows, _ + 1) { row =>
86+
if(row > 0) buf.append(' ')
87+
buf.append('[')
88+
cfor(0)(_ < tile.cols, _ + 1) { col =>
89+
val v = tile.getDouble(col, row)
90+
if (isNoData(v)) buf.append(ND)
91+
else buf.append(fmt.format(v))
92+
93+
if (col < tile.cols - 1)
94+
buf.append(',')
95+
}
96+
buf.append(']')
97+
if (row < tile.rows - 1)
98+
buf.append(",\n")
99+
}
100+
buf.append("]")
101+
buf.toString()
102+
}
103+
}
78104
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* This software is licensed under the Apache 2 license, quoted below.
3+
*
4+
* Copyright 2019 Astraea, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
7+
* use this file except in compliance with the License. You may obtain a copy of
8+
* the License at
9+
*
10+
* [http://www.apache.org/licenses/LICENSE-2.0]
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15+
* License for the specific language governing permissions and limitations under
16+
* the License.
17+
*
18+
* SPDX-License-Identifier: Apache-2.0
19+
*
20+
*/
21+
22+
package org.locationtech.rasterframes.expressions.transformers
23+
24+
import geotrellis.raster.ArrayMultibandTile
25+
import org.apache.spark.sql.Column
26+
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult
27+
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.{TypeCheckFailure, TypeCheckSuccess}
28+
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
29+
import org.apache.spark.sql.catalyst.expressions.{Expression, ExpressionDescription, TernaryExpression}
30+
import org.apache.spark.sql.rf.TileUDT
31+
import org.apache.spark.sql.types.DataType
32+
import org.locationtech.rasterframes._
33+
import org.locationtech.rasterframes.encoders.CatalystSerializer._
34+
import org.locationtech.rasterframes.expressions.DynamicExtractors.tileExtractor
35+
import org.locationtech.rasterframes.expressions.row
36+
import org.locationtech.rasterframes.tiles.ProjectedRasterTile
37+
38+
/**
39+
* Expression to combine the given tile columns into an 32-bit RGB composite.
40+
* Tiles in each row will first be and-ed with 0xFF, bit shifted, and or-ed into a single 32-bit word.
41+
* @param red tile column to represent red channel
42+
* @param green tile column to represent green channel
43+
* @param blue tile column to represent blue channel
44+
*/
45+
@ExpressionDescription(
46+
usage = "_FUNC_(red, green, blue) - Combines the given tile columns into an 32-bit RGB composite.",
47+
arguments = """
48+
Arguments:
49+
* red - tile column representing the red channel
50+
* green - tile column representing the green channel
51+
* blue - tile column representing the blue channel"""
52+
)
53+
case class RGBComposite(red: Expression, green: Expression, blue: Expression) extends TernaryExpression
54+
with CodegenFallback {
55+
56+
override def nodeName: String = "rf_rgb_composite"
57+
58+
override def dataType: DataType = if(
59+
red.dataType.conformsTo[ProjectedRasterTile] ||
60+
blue.dataType.conformsTo[ProjectedRasterTile] ||
61+
green.dataType.conformsTo[ProjectedRasterTile]
62+
) red.dataType
63+
else TileType
64+
65+
override def children: Seq[Expression] = Seq(red, green, blue)
66+
67+
override def checkInputDataTypes(): TypeCheckResult = {
68+
if (!tileExtractor.isDefinedAt(red.dataType)) {
69+
TypeCheckFailure(s"Red channel input type '${red.dataType}' does not conform to a raster type.")
70+
}
71+
else if (!tileExtractor.isDefinedAt(green.dataType)) {
72+
TypeCheckFailure(s"Green channel input type '${green.dataType}' does not conform to a raster type.")
73+
}
74+
else if (!tileExtractor.isDefinedAt(blue.dataType)) {
75+
TypeCheckFailure(s"Blue channel input type '${blue.dataType}' does not conform to a raster type.")
76+
}
77+
else TypeCheckSuccess
78+
}
79+
80+
override protected def nullSafeEval(input1: Any, input2: Any, input3: Any): Any = {
81+
val (r, rc) = tileExtractor(red.dataType)(row(input1))
82+
val (g, gc) = tileExtractor(green.dataType)(row(input2))
83+
val (b, bc) = tileExtractor(blue.dataType)(row(input3))
84+
85+
// Pick the first available TileContext, if any, and reassociate with the result
86+
val ctx = Seq(rc, gc, bc).flatten.headOption
87+
val composite = ArrayMultibandTile(
88+
r.rescale(0, 255), g.rescale(0, 255), b.rescale(0, 255)
89+
).color()
90+
ctx match {
91+
case Some(c) => c.toProjectRasterTile(composite).toInternalRow
92+
case None =>
93+
implicit val tileSer = TileUDT.tileSerializer
94+
composite.toInternalRow
95+
}
96+
}
97+
}
98+
99+
object RGBComposite {
100+
def apply(red: Column, green: Column, blue: Column): Column =
101+
new Column(RGBComposite(red.expr, green.expr, blue.expr))
102+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* This software is licensed under the Apache 2 license, quoted below.
3+
*
4+
* Copyright 2019 Astraea, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
7+
* use this file except in compliance with the License. You may obtain a copy of
8+
* the License at
9+
*
10+
* [http://www.apache.org/licenses/LICENSE-2.0]
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15+
* License for the specific language governing permissions and limitations under
16+
* the License.
17+
*
18+
* SPDX-License-Identifier: Apache-2.0
19+
*
20+
*/
21+
22+
package org.locationtech.rasterframes.expressions.transformers
23+
24+
import geotrellis.raster.Tile
25+
import geotrellis.raster.render.ColorRamp
26+
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
27+
import org.apache.spark.sql.catalyst.expressions.{Expression, ExpressionDescription}
28+
import org.apache.spark.sql.types.{BinaryType, DataType}
29+
import org.apache.spark.sql.{Column, TypedColumn}
30+
import org.locationtech.rasterframes.expressions.UnaryRasterOp
31+
import org.locationtech.rasterframes.model.TileContext
32+
33+
/**
34+
* Converts a tile into a PNG encoded byte array.
35+
* @param child tile column
36+
* @param ramp color ramp to use for non-composite tiles.
37+
*/
38+
abstract class RenderPNG(child: Expression, ramp: Option[ColorRamp]) extends UnaryRasterOp with CodegenFallback with Serializable {
39+
override def dataType: DataType = BinaryType
40+
override protected def eval(tile: Tile, ctx: Option[TileContext]): Any = {
41+
val png = ramp.map(tile.renderPng).getOrElse(tile.renderPng())
42+
png.bytes
43+
}
44+
}
45+
46+
object RenderPNG {
47+
import org.locationtech.rasterframes.encoders.SparkBasicEncoders._
48+
49+
@ExpressionDescription(
50+
usage = "_FUNC_(tile) - Encode the given tile into a RGB composite PNG. Assumes the red, green, and " +
51+
"blue channels are encoded as 8-bit channels within the 32-bit word.",
52+
arguments = """
53+
Arguments:
54+
* tile - tile to render"""
55+
)
56+
case class RenderCompositePNG(child: Expression) extends RenderPNG(child, None) {
57+
override def nodeName: String = "rf_render_png"
58+
}
59+
60+
object RenderCompositePNG {
61+
def apply(red: Column, green: Column, blue: Column): TypedColumn[Any, Array[Byte]] =
62+
new Column(RenderCompositePNG(RGBComposite(red.expr, green.expr, blue.expr))).as[Array[Byte]]
63+
}
64+
65+
@ExpressionDescription(
66+
usage = "_FUNC_(tile) - Encode the given tile as a PNG using a color ramp with assignemnts from quantile computation",
67+
arguments = """
68+
Arguments:
69+
* tile - tile to render"""
70+
)
71+
case class RenderColorRampPNG(child: Expression, colors: ColorRamp) extends RenderPNG(child, Some(colors)) {
72+
override def nodeName: String = "rf_render_png"
73+
}
74+
75+
object RenderColorRampPNG {
76+
def apply(tile: Column, colors: ColorRamp): TypedColumn[Any, Array[Byte]] =
77+
new Column(RenderColorRampPNG(tile.expr, colors)).as[Array[Byte]]
78+
}
79+
}

core/src/main/scala/org/locationtech/rasterframes/extensions/SinglebandGeoTiffMethods.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.apache.spark.sql.{DataFrame, Row, SparkSession}
3030
import org.locationtech.rasterframes._
3131
import org.locationtech.rasterframes.encoders.CatalystSerializer._
3232
import org.locationtech.rasterframes.model.TileDimensions
33+
import org.locationtech.rasterframes.tiles.ProjectedRasterTile
3334

3435
trait SinglebandGeoTiffMethods extends MethodExtensions[SinglebandGeoTiff] {
3536
def toDF(dims: TileDimensions = NOMINAL_TILE_DIMS)(implicit spark: SparkSession): DataFrame = {
@@ -56,4 +57,6 @@ trait SinglebandGeoTiffMethods extends MethodExtensions[SinglebandGeoTiff] {
5657

5758
spark.createDataFrame(spark.sparkContext.makeRDD(rows, 1), schema)
5859
}
60+
61+
def toProjectedRasterTile: ProjectedRasterTile = ProjectedRasterTile(self.projectedRaster)
5962
}

0 commit comments

Comments
 (0)