Skip to content

Commit 316081c

Browse files
committed
Add rf_local_data and rf_local_no_data #244
Signed-off-by: Jason T. Brown <jason@astraea.earth>
1 parent 0a6a46a commit 316081c

File tree

9 files changed

+161
-2
lines changed

9 files changed

+161
-2
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@ trait RasterFunctions {
371371
/** Cellwise inequality comparison between a tile and a scalar. */
372372
def rf_local_unequal[T: Numeric](tileCol: Column, value: T): Column = Unequal(tileCol, value)
373373

374+
/** Return a tile with ones where the input is NoData, otherwise zero */
375+
def rf_local_no_data(tileCol: Column): Column = Undefined(tileCol)
376+
377+
/** Return a tile with zeros where the input is NoData, otherwise one*/
378+
def rf_local_data(tileCol: Column): Column = Defined(tileCol)
379+
374380
/** Round cell values to nearest integer without chaning cell type. */
375381
def rf_round(tileCol: Column): Column = Round(tileCol)
376382

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.localops
23+
24+
import geotrellis.raster.Tile
25+
import org.apache.spark.sql.Column
26+
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
27+
import org.apache.spark.sql.catalyst.expressions.{Expression, ExpressionDescription}
28+
import org.locationtech.rasterframes.expressions.{NullToValue, UnaryLocalRasterOp}
29+
30+
@ExpressionDescription(
31+
usage = "_FUNC_(tile) - Return a tile with zeros where the input is NoData, otherwise one.",
32+
arguments = """
33+
Arguments:
34+
* tile - tile column to inspect""",
35+
examples = """
36+
Examples:
37+
> SELECT _FUNC_(tile);
38+
..."""
39+
)
40+
case class Defined(child: Expression) extends UnaryLocalRasterOp
41+
with NullToValue with CodegenFallback {
42+
override def nodeName: String = "rf_local_data"
43+
override def na: Any = null
44+
override protected def op(child: Tile): Tile = child.localDefined()
45+
}
46+
object Defined{
47+
def apply(tile: Column): Column = new Column(Defined(tile.expr))
48+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.localops
23+
24+
import geotrellis.raster.Tile
25+
import org.apache.spark.sql.Column
26+
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
27+
import org.apache.spark.sql.catalyst.expressions.{Expression, ExpressionDescription}
28+
import org.locationtech.rasterframes.expressions.{NullToValue, UnaryLocalRasterOp}
29+
30+
@ExpressionDescription(
31+
usage = "_FUNC_(tile) - Return a tile with ones where the input is NoData, otherwise zero.",
32+
arguments = """
33+
Arguments:
34+
* tile - tile column to inspect""",
35+
examples = """
36+
Examples:
37+
> SELECT _FUNC_(tile);
38+
..."""
39+
)
40+
case class Undefined(child: Expression) extends UnaryLocalRasterOp
41+
with NullToValue with CodegenFallback {
42+
override def nodeName: String = "rf_local_no_data"
43+
override def na: Any = null
44+
override protected def op(child: Tile): Tile = child.localUndefined()
45+
}
46+
object Undefined{
47+
def apply(tile: Column): Column = new Column(Undefined(tile.expr))
48+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ package object expressions {
8787
registry.registerExpression[GreaterEqual]("rf_local_greater_equal")
8888
registry.registerExpression[Equal]("rf_local_equal")
8989
registry.registerExpression[Unequal]("rf_local_unequal")
90+
registry.registerExpression[Undefined]("rf_local_no_data")
91+
registry.registerExpression[Defined]("rf_local_data")
9092
registry.registerExpression[Sum]("rf_tile_sum")
9193
registry.registerExpression[Round]("rf_round")
9294
registry.registerExpression[Abs]("rf_abs")

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,4 +912,19 @@ class RasterFunctionsSpec extends TestEnvironment with RasterMatchers {
912912
countNewNd should be (0L)
913913

914914
}
915+
916+
it("should return local data and nodata"){
917+
checkDocs("rf_local_data")
918+
checkDocs("rf_local_no_data")
919+
920+
val df = Seq(randNDPRT).toDF("t")
921+
.withColumn("ld", rf_local_data($"t"))
922+
.withColumn("lnd", rf_local_no_data($"t"))
923+
924+
val ndResult = df.select($"lnd").as[Tile].first()
925+
ndResult should be (randNDPRT.localUndefined())
926+
927+
val dResult = df.select($"ld").as[Tile].first()
928+
dResult should be (randNDPRT.localDefined())
929+
}
915930
}

docs/src/main/paradox/release-notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
### 0.8.1
66

7-
* Added `rf_interpret_cell_type_as` raster function.
7+
* Added `rf_local_no_data`, `rf_local_data` and `rf_interpret_cell_type_as` raster functions.
88
* Added `toMarkdown()` and `toHTML()` extension methods for `DataFrame`, and registered them with the IPython formatter system when `rf_ipython` is imported.
99
* Fixed: Removed false return type garauntee in cases where an `Expression` accepts either `Tile` or `ProjectedRasterTile` [(#295)](https://github.com/locationtech/rasterframes/issues/295)
1010

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,20 @@ Generate a `tile` with the values from `data_tile`, with NoData in cells where t
223223

224224
Returns true if `tile` contains only NoData. By definition returns false if cell type does not support NoData. To count NoData cells or data cells, see @ref:[`rf_no_data_cells`](reference.md#rf-no-data-cells), @ref:[`rf_data_cells`](reference.md#rf-data-cells), @ref:[`rf_agg_no_data_cells`](reference.md#rf-agg-no-data-cells), @ref:[`rf_agg_data_cells`](reference.md#rf-agg-data-cells), @ref:[`rf_agg_local_no_data_cells`](reference.md#rf-agg-local-no-data-cells), and @ref:[`rf_agg_local_data_cells`](reference.md#rf-agg-local-data-cells). This function is distinguished from @ref:[`rf_for_all`](reference.md#rf-for-all), which tests that values are not NoData and nonzero.
225225

226+
### rf_local_no_data
227+
228+
Tile rf_local_no_data(Tile tile)
229+
230+
Returns a tile with values of 1 in each cell where the input tile contains NoData. Otherwise values are 0.
231+
232+
### rf_local_data
233+
234+
Tile rf_local_no_data(Tile tile)
235+
236+
Returns a tile with values of 0 in each cell where the input tile contains NoData. Otherwise values are 1.
237+
238+
### rf_local_data
239+
226240
### rf_with_no_data
227241

228242
Tile rf_with_no_data(Tile tile, Double no_data_value)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,13 @@ def rf_local_unequal_int(tile_col, scalar):
246246
"""Return a Tile with values equal 1 if the cell is not equal to a scalar, otherwise 0"""
247247
return _apply_scalar_to_tile('rf_local_unequal_int', tile_col, scalar)
248248

249+
def rf_local_no_data(tile_col):
250+
"""Return a tile with ones where the input is NoData, otherwise zero."""
251+
return _apply_column_function('rf_local_no_data', tile_col)
252+
253+
def rf_local_data(tile_col):
254+
"""Return a tile with zeros where the input is NoData, otherwise one."""
255+
return _apply_column_function('rf_local_data', tile_col)
249256

250257
def _apply_column_function(name, *args):
251258
jfcn = RFContext.active().lookup(name)

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,28 @@ def test_rf_interpret_cell_type_as(self):
288288
df = self.spark.createDataFrame([
289289
Row(t=Tile(np.array([[1, 3, 4], [5, 0, 3]]), CellType.uint8().with_no_data_value(5)))
290290
])
291-
df = df.withColumn('tile', rf_interpret_cell_type_as('t', 'uint8ud3')) # threes become ND
291+
df = df.withColumn('tile', rf_interpret_cell_type_as('t', 'uint8ud3')) # threes become ND
292292
result = df.select(rf_tile_sum(rf_local_equal('t', lit(3))).alias('threes')).first()['threes']
293293
self.assertEqual(result, 2)
294294

295295
result_5 = df.select(rf_tile_sum(rf_local_equal('t', lit(5))).alias('fives')).first()['fives']
296296
self.assertEqual(result_5, 0)
297+
298+
def test_rf_local_data_and_no_data(self):
299+
from pyspark.sql import Row
300+
from pyrasterframes.rf_types import Tile
301+
import numpy as np
302+
from numpy.testing import assert_equal
303+
304+
t = Tile(np.array([[1, 3, 4], [5, 0, 3]]), CellType.uint8().with_no_data_value(5))
305+
#note the convert is due to issue #188
306+
df = self.spark.createDataFrame([Row(t=t)])\
307+
.withColumn('lnd', rf_convert_cell_type(rf_local_no_data('t'), 'uint8')) \
308+
.withColumn('ld', rf_convert_cell_type(rf_local_data('t'), 'uint8'))
309+
310+
result = df.first()
311+
result_nd = result['lnd']
312+
assert_equal(result_nd.cells, t.cells.mask)
313+
314+
result_d = result['ld']
315+
assert_equal(result_d.cells, np.invert(t.cells.mask))

0 commit comments

Comments
 (0)