Skip to content

Commit b1ea065

Browse files
committed
Add expression and raster functions around Tile.interpretAs called rf_interpret_cell_type_as
Signed-off-by: Jason T. Brown <[email protected]>
1 parent 0506fb1 commit b1ea065

File tree

8 files changed

+171
-3
lines changed

8 files changed

+171
-3
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ trait RasterFunctions {
9494
/** Change the Tile's cell type */
9595
def rf_convert_cell_type(col: Column, cellTypeName: String): Column = SetCellType(col, cellTypeName)
9696

97+
/** Change the Tile's cell type */
98+
def rf_convert_cell_type(col: Column, cellType: Column): Column = SetCellType(col, cellType)
99+
100+
/** Change the interpretation of the Tile's cell values according to specified CellType */
101+
def rf_interpret_cell_type_as(col: Column, cellType: CellType): Column = InterpretAs(col, cellType)
102+
103+
/** Change the interpretation of the Tile's cell values according to specified CellType */
104+
def rf_interpret_cell_type_as(col: Column, cellTypeName: String): Column = InterpretAs(col, cellTypeName)
105+
106+
/** Change the interpretation of the Tile's cell values according to specified CellType */
107+
def rf_interpret_cell_type_as(col: Column, cellType: Column): Column = InterpretAs(col, cellType)
108+
97109
/** Resample tile to different size based on scalar factor or tile whose dimension to match. Scalar less
98110
* than one will downsample tile; greater than one will upsample. Uses nearest-neighbor. */
99111
def rf_resample[T: Numeric](tileCol: Column, factorValue: T) = Resample(tileCol, factorValue)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ package object expressions {
6868
registry.registerExpression[ExplodeTiles]("rf_explode_tiles")
6969
registry.registerExpression[GetCellType]("rf_cell_type")
7070
registry.registerExpression[SetCellType]("rf_convert_cell_type")
71+
registry.registerExpression[InterpretAs]("rf_interpret_cell_type_as")
7172
registry.registerExpression[SetNoDataValue]("rf_with_no_data")
7273
registry.registerExpression[GetDimensions]("rf_dimensions")
7374
registry.registerExpression[ExtentToGeometry]("st_geometry")
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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.CellType
25+
import org.apache.spark.sql.Column
26+
import org.apache.spark.sql.catalyst.InternalRow
27+
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult
28+
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.{TypeCheckFailure, TypeCheckSuccess}
29+
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
30+
import org.apache.spark.sql.catalyst.expressions.{BinaryExpression, Expression, ExpressionDescription}
31+
import org.apache.spark.sql.functions.lit
32+
import org.apache.spark.sql.rf.{TileUDT}
33+
import org.apache.spark.sql.types._
34+
import org.apache.spark.unsafe.types.UTF8String
35+
import org.locationtech.rasterframes.encoders.CatalystSerializer._
36+
import org.locationtech.rasterframes.expressions.DynamicExtractors.tileExtractor
37+
import org.locationtech.rasterframes.expressions.row
38+
39+
/**
40+
* Change the CellType of a Tile
41+
*
42+
* @since 9/11/18
43+
*/
44+
45+
@ExpressionDescription(
46+
usage = "_FUNC_(tile, value) - Change the interpretation of the Tile's cell values according to specified CellType",
47+
arguments = """
48+
Arguments:
49+
* tile - left-hand-side tile
50+
* rhs - a cell type definition""",
51+
examples = """
52+
Examples:
53+
> SELECT _FUNC_(tile, 'int16ud0');
54+
..."""
55+
)
56+
case class InterpretAs(tile: Expression, cellType: Expression)
57+
extends BinaryExpression with CodegenFallback {
58+
def left = tile
59+
def right = cellType
60+
override def nodeName: String = "rf_interpret_cell_type_as"
61+
override def dataType: DataType = left.dataType
62+
63+
override def checkInputDataTypes(): TypeCheckResult = {
64+
if (!tileExtractor.isDefinedAt(left.dataType))
65+
TypeCheckFailure(s"Input type '${left.dataType}' does not conform to a raster type.")
66+
else
67+
right.dataType match {
68+
case StringType => TypeCheckSuccess
69+
case t if t.conformsTo[CellType] => TypeCheckSuccess
70+
case _ =>
71+
TypeCheckFailure(s"Expected CellType but received '${right.dataType.simpleString}'")
72+
}
73+
}
74+
75+
private def toCellType(datum: Any): CellType = {
76+
right.dataType match {
77+
case StringType =>
78+
val text = datum.asInstanceOf[UTF8String].toString
79+
CellType.fromName(text)
80+
case st if st.conformsTo[CellType] =>
81+
row(datum).to[CellType]
82+
}
83+
}
84+
85+
override protected def nullSafeEval(tileInput: Any, ctInput: Any): InternalRow = {
86+
implicit val tileSer = TileUDT.tileSerializer
87+
88+
val (tile, ctx) = tileExtractor(left.dataType)(row(tileInput))
89+
val ct = toCellType(ctInput)
90+
val result = tile.interpretAs(ct)
91+
92+
ctx match {
93+
case Some(c) => c.toProjectRasterTile(result).toInternalRow
94+
case None => result.toInternalRow
95+
}
96+
}
97+
}
98+
99+
object InterpretAs{
100+
def apply(tile: Column, cellType: CellType): Column =
101+
new Column(new InterpretAs(tile.expr, lit(cellType.name).expr))
102+
def apply(tile: Column, cellType: String): Column =
103+
new Column(new InterpretAs(tile.expr, lit(cellType).expr))
104+
def apply(tile: Column, cellType: Column): Column =
105+
new Column(new InterpretAs(tile.expr, cellType.expr))
106+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,6 @@ object SetCellType {
101101
new Column(new SetCellType(tile.expr, lit(cellType.name).expr))
102102
def apply(tile: Column, cellType: String): Column =
103103
new Column(new SetCellType(tile.expr, lit(cellType).expr))
104+
def apply(tile: Column, cellType: Column): Column =
105+
new Column(new SetCellType(tile.expr, cellType.expr))
104106
}

core/src/test/scala/org/locationtech/rasterframes/RasterFunctionsSpec.scala

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class RasterFunctionsSpec extends TestEnvironment with RasterMatchers {
5252
lazy val six = ProjectedRasterTile(three * two, three.extent, three.crs)
5353
lazy val nd = TestData.projectedRasterTile(cols, rows, -2, extent, crs, ct)
5454
lazy val randPRT = TestData.projectedRasterTile(cols, rows, scala.util.Random.nextInt(), extent, crs, ct)
55-
lazy val randNDPRT = TestData.injectND(numND)(randPRT)
55+
lazy val randNDPRT: Tile = TestData.injectND(numND)(randPRT)
5656

5757
lazy val randDoubleTile = TestData.projectedRasterTile(cols, rows, scala.util.Random.nextGaussian(), extent, crs, DoubleConstantNoDataCellType)
5858
lazy val randDoubleNDTile = TestData.injectND(numND)(randDoubleTile)
@@ -893,4 +893,23 @@ class RasterFunctionsSpec extends TestEnvironment with RasterMatchers {
893893

894894
checkDocs("rf_resample")
895895
}
896+
it("should interpret cell values with a specified cell type") {
897+
checkDocs("rf_interpret_cell_type_as")
898+
val df = Seq(randNDPRT).toDF("t")
899+
.withColumn("tile", rf_interpret_cell_type_as($"t", "int8raw"))
900+
val resultTile = df.select("tile").as[Tile].first()
901+
902+
resultTile.cellType should be (CellType.fromName("int8raw"))
903+
// should have same number of values that are -2 the old ND
904+
val countOldNd = df.select(
905+
rf_tile_sum(rf_local_equal($"tile", ct.noDataValue)),
906+
rf_no_data_cells($"t")
907+
).first()
908+
countOldNd._1 should be (countOldNd._2)
909+
910+
// should not have no data any more (raw type)
911+
val countNewNd = df.select(rf_no_data_cells($"tile")).first()
912+
countNewNd should be (0L)
913+
914+
}
896915
}

pyrasterframes/src/main/python/docs/reference.pymd

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,17 @@ Example: `SELECT rf_mk_crs('EPSG:4326')`
106106

107107
### rf_convert_cell_type
108108

109-
Tile rf_convert_cell_type(Tile tileCol, String cellType)
109+
Tile rf_convert_cell_type(Tile tile_col, CellType cell_type)
110+
Tile rf_convert_cell_type(Tile tile_col, String cell_type)
110111

111-
Convert `tileCol` to a different cell type. Available cell types can be retrieved with the @ref:[rf_cell_types](reference.md#rf-cell-types) function.
112+
Convert `tile_col` to a different cell type. In Python you can pass a CellType object to `cell_type`.
113+
114+
### rf_interpret_cell_type_as
115+
116+
Tile rf_interpret_cell_type_as(Tile tile_col, CellType cell_type)
117+
Tile rf_interpret_cell_type_as(Tile tile_col, String cell_type)
118+
119+
Change the interpretation of the `tile_col`'s cell values according to specified `cell_type`. In Python you can pass a CellType object to `cell_type`.
112120

113121
### rf_resample
114122

pyrasterframes/src/main/python/pyrasterframes/rasterfunctions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ def rf_convert_cell_type(tile_col, cell_type):
7272
jfcn = RFContext.active().lookup('rf_convert_cell_type')
7373
return Column(jfcn(_to_java_column(tile_col), _parse_cell_type(cell_type)))
7474

75+
def rf_interpret_cell_type_as(tile_col, cell_type):
76+
"""Change the interpretation of the tile_col's cell values according to specified cell_type"""
77+
jfcn = RFContext.active().lookup('rf_interpret_cell_type_as')
78+
return Column(jfcn(_to_java_column(tile_col), _parse_cell_type(cell_type)))
79+
7580

7681
def rf_make_constant_tile(scalar_value, num_cols, num_rows, cell_type=CellType.float64()):
7782
"""Constructor for constant tile column"""

pyrasterframes/src/main/python/tests/RasterFunctionsTests.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,18 @@ def test_cell_type_in_functions(self):
279279
self.assertEqual(counts["data2"], 0)
280280
self.assertEqual(counts["nodata2"], 3 * 4)
281281

282+
283+
def test_rf_interpret_cell_type_as(self):
284+
from pyspark.sql import Row
285+
from pyrasterframes.rf_types import Tile
286+
import numpy as np
287+
288+
df = self.spark.createDataFrame([
289+
Row(t=Tile(np.array([[1, 3, 4], [5, 0, 3]]), CellType.uint8().with_no_data_value(5)))
290+
])
291+
df = df.withColumn('tile', rf_interpret_cell_type_as('t', 'uint8ud3')) # threes become ND
292+
result = df.select(rf_tile_sum(rf_local_equal('t', lit(3))).alias('threes')).first()['threes']
293+
self.assertEqual(result, 2)
294+
295+
result_5 = df.select(rf_tile_sum(rf_local_equal('t', lit(5))).alias('fives')).first()['fives']
296+
self.assertEqual(result_5, 0)

0 commit comments

Comments
 (0)