Skip to content

Commit c7a850e

Browse files
committed
Merge remote-tracking branch 'locationtech/develop' into feature/nodata-add-doc
2 parents 9e07cae + 6bf41f2 commit c7a850e

File tree

79 files changed

+2306
-1239
lines changed

Some content is hidden

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

79 files changed

+2306
-1239
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
<img src="docs/src/main/paradox/_template/images/RasterFramesLogo.png" width="300px"/><sup style="vertical-align: top;">&trade;</sup>
1+
<img src="docs/src/main/paradox/_template/images/RasterFramesLogo.png" width="300px"/><sup style="vertical-align: top;">&reg;</sup>
22

33
[![Join the chat at https://gitter.im/s22s/raster-frames](https://badges.gitter.im/s22s/raster-frames.svg)](https://gitter.im/s22s/raster-frames?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
44

55
_RasterFrames™_ brings the power of Spark DataFrames to geospatial raster data, empowered by the map algebra and tile layer operations of [GeoTrellis](https://geotrellis.io/).
66

7-
<img src="docs/src/main/paradox/RasterFramePipeline.svg" width="600px"/>
7+
<img src="docs/src/main/paradox/RasterFramePipeline.png" width="600px"/>
88

99
Please see the [Getting Started](http://rasterframes.io/getting-started.html) section of the Users' Manual to start using RasterFrames.
1010

@@ -54,6 +54,6 @@ The `pyrasterframes` build instructions are located at [pyrasterframes/src/main/
5454

5555
## Copyright and License
5656

57-
RasterFrames is released under the Apache 2.0 License, copyright Astraea, Inc. 2017-2018.
57+
RasterFrames is released under the Apache 2.0 License, copyright Astraea, Inc. 2017-2019.
5858

5959

core/src/it/scala/org/locationtech/rasterframes/ref/RasterSourceIT.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ class RasterSourceIT extends TestEnvironment with TestData {
6363
}
6464
}
6565

66-
if (RasterSource.IsGDAL.hasGDAL) {
67-
println("GDAL version: " + GDALWarp.get_version_info("--version"))
66+
if (GDALRasterSource.hasGDAL) {
67+
println("GDAL version: " + GDALRasterSource.gdalVersion())
6868

6969
describe("GDAL support") {
7070

core/src/main/resources/reference.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ vlm.gdal {
1212
//CPL_DEBUG = "OFF"
1313
AWS_REQUEST_PAYER = "requester"
1414
GDAL_DISABLE_READDIR_ON_OPEN = "YES"
15-
CPL_VSIL_CURL_ALLOWED_EXTENSIONS = ".tif,.tiff,.jp2,.mrf,.idx,.lrc,.mrf.aux.xml"
15+
CPL_VSIL_CURL_ALLOWED_EXTENSIONS = ".tif,.tiff,.jp2,.mrf,.idx,.lrc,.mrf.aux.xml,.vrt"
1616
}
1717
// set this to `false` if CPL_DEBUG is `ON`
1818
useExceptions = true

core/src/main/scala/org/locationtech/rasterframes/encoders/CatalystSerializer.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,9 @@ object CatalystSerializer extends StandardSerializers {
154154
implicit class WithFromRow(val r: Row) extends AnyVal {
155155
def to[T >: Null: CatalystSerializer]: T = if (r == null) null else CatalystSerializer[T].fromRow(r)
156156
}
157+
158+
implicit class WithTypeConformity(val left: DataType) extends AnyVal {
159+
def conformsTo[T >: Null: CatalystSerializer]: Boolean =
160+
org.apache.spark.sql.rf.WithTypeConformity(left).conformsTo(schemaOf[T])
161+
}
157162
}

core/src/main/scala/org/locationtech/rasterframes/encoders/StandardEncoders.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ package org.locationtech.rasterframes.encoders
2424
import java.net.URI
2525
import java.sql.Timestamp
2626

27-
import org.locationtech.rasterframes.model._
2827
import org.locationtech.rasterframes.stats.{CellHistogram, CellStatistics, LocalCellStatistics}
2928
import org.locationtech.jts.geom.Envelope
3029
import geotrellis.proj4.CRS
3130
import geotrellis.raster.{CellSize, CellType, Raster, Tile, TileLayout}
3231
import geotrellis.spark.tiling.LayoutDefinition
3332
import geotrellis.spark.{KeyBounds, SpaceTimeKey, SpatialKey, TemporalKey, TemporalProjectedExtent, TileLayerMetadata}
3433
import geotrellis.vector.{Extent, ProjectedExtent}
34+
import org.apache.spark.sql.{Encoder, Encoders}
3535
import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
3636
import org.locationtech.geomesa.spark.jts.encoders.SpatialEncoders
3737
import org.locationtech.rasterframes.model.{CellContext, Cells, TileContext, TileDataContext}
@@ -70,6 +70,7 @@ trait StandardEncoders extends SpatialEncoders {
7070
implicit def cellsEncoder: ExpressionEncoder[Cells] = Cells.encoder
7171
implicit def tileContextEncoder: ExpressionEncoder[TileContext] = TileContext.encoder
7272
implicit def tileDataContextEncoder: ExpressionEncoder[TileDataContext] = TileDataContext.encoder
73+
implicit def extentTilePairEncoder: Encoder[(ProjectedExtent, Tile)] = Encoders.tuple(projectedExtentEncoder, singlebandTileEncoder)
7374

7475

7576
}

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121

2222
package org.locationtech.rasterframes.expressions
2323

24+
import geotrellis.proj4.CRS
2425
import geotrellis.raster.{CellGrid, Tile}
2526
import org.apache.spark.sql.Row
2627
import org.apache.spark.sql.catalyst.InternalRow
27-
import org.apache.spark.sql.rf.{TileUDT, _}
28+
import org.apache.spark.sql.rf.{RasterSourceUDT, TileUDT}
2829
import org.apache.spark.sql.types._
30+
import org.apache.spark.unsafe.types.UTF8String
2931
import org.locationtech.rasterframes.encoders.CatalystSerializer._
30-
import org.locationtech.rasterframes.model.TileContext
32+
import org.locationtech.rasterframes.model.{LazyCRS, TileContext}
3133
import org.locationtech.rasterframes.ref.{ProjectedRasterLike, RasterRef, RasterSource}
3234
import org.locationtech.rasterframes.tiles.ProjectedRasterTile
3335

@@ -38,7 +40,7 @@ object DynamicExtractors {
3840
case _: TileUDT =>
3941
(row: InternalRow) =>
4042
(row.to[Tile](TileUDT.tileSerializer), None)
41-
case t if t.conformsTo(schemaOf[ProjectedRasterTile]) =>
43+
case t if t.conformsTo[ProjectedRasterTile] =>
4244
(row: InternalRow) => {
4345
val prt = row.to[ProjectedRasterTile]
4446
(prt, Some(TileContext(prt)))
@@ -48,7 +50,7 @@ object DynamicExtractors {
4850
lazy val rowTileExtractor: PartialFunction[DataType, Row => (Tile, Option[TileContext])] = {
4951
case _: TileUDT =>
5052
(row: Row) => (row.to[Tile](TileUDT.tileSerializer), None)
51-
case t if t.conformsTo(schemaOf[ProjectedRasterTile]) =>
53+
case t if t.conformsTo[ProjectedRasterTile] =>
5254
(row: Row) => {
5355
val prt = row.to[ProjectedRasterTile]
5456
(prt, Some(TileContext(prt)))
@@ -59,9 +61,9 @@ object DynamicExtractors {
5961
lazy val projectedRasterLikeExtractor: PartialFunction[DataType, InternalRow ProjectedRasterLike] = {
6062
case _: RasterSourceUDT
6163
(row: InternalRow) => row.to[RasterSource](RasterSourceUDT.rasterSourceSerializer)
62-
case t if t.conformsTo(schemaOf[ProjectedRasterTile]) =>
64+
case t if t.conformsTo[ProjectedRasterTile] =>
6365
(row: InternalRow) => row.to[ProjectedRasterTile]
64-
case t if t.conformsTo(schemaOf[RasterRef]) =>
66+
case t if t.conformsTo[RasterRef] =>
6567
(row: InternalRow) => row.to[RasterRef]
6668
}
6769

@@ -71,12 +73,19 @@ object DynamicExtractors {
7173
(row: InternalRow) => row.to[Tile](TileUDT.tileSerializer)
7274
case _: RasterSourceUDT =>
7375
(row: InternalRow) => row.to[RasterSource](RasterSourceUDT.rasterSourceSerializer)
74-
case t if t.conformsTo(schemaOf[RasterRef])
76+
case t if t.conformsTo[RasterRef]
7577
(row: InternalRow) => row.to[RasterRef]
76-
case t if t.conformsTo(schemaOf[ProjectedRasterTile]) =>
78+
case t if t.conformsTo[ProjectedRasterTile] =>
7779
(row: InternalRow) => row.to[ProjectedRasterTile]
7880
}
7981

82+
lazy val crsExtractor: PartialFunction[DataType, Any => CRS] = {
83+
case _: StringType =>
84+
(v: Any) => LazyCRS(v.asInstanceOf[UTF8String].toString)
85+
case t if t.conformsTo[CRS] =>
86+
(v: Any) => v.asInstanceOf[InternalRow].to[CRS]
87+
}
88+
8089
sealed trait TileOrNumberArg
8190
sealed trait NumberArg extends TileOrNumberArg
8291
case class TileArg(tile: Tile, ctx: Option[TileContext]) extends TileOrNumberArg
@@ -113,4 +122,5 @@ object DynamicExtractors {
113122
case c: Char => IntegerArg(c.toInt)
114123
}
115124
}
125+
116126
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
3030
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
3131
import org.apache.spark.sql.catalyst.expressions.{ScalaUDF, _}
3232
import org.apache.spark.sql.jts.AbstractGeometryUDT
33-
import org.apache.spark.sql.rf.WithTypeConformity
3433
import org.apache.spark.sql.types._
3534
import org.locationtech.geomesa.spark.jts.udf.SpatialRelationFunctions._
3635

@@ -48,7 +47,7 @@ abstract class SpatialRelation extends BinaryExpression
4847
case r: InternalRow
4948
expr.dataType match {
5049
case udt: AbstractGeometryUDT[_] udt.deserialize(r)
51-
case dt if dt.conformsTo(schemaOf[Extent]) =>
50+
case dt if dt.conformsTo[Extent] =>
5251
val extent = r.to[Extent]
5352
extent.jtsGeom
5453
}

core/src/main/scala/org/locationtech/rasterframes/expressions/aggregates/LocalMeanAggregate.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
package org.locationtech.rasterframes.expressions.aggregates
2323

2424
import org.locationtech.rasterframes.expressions.UnaryRasterAggregate
25-
import org.locationtech.rasterframes.expressions.localops.{Add => AddTiles, Divide => DivideTiles}
25+
import org.locationtech.rasterframes.expressions.localops.{BiasedAdd, Divide => DivideTiles}
2626
import org.locationtech.rasterframes.expressions.transformers.SetCellType
2727
import geotrellis.raster.Tile
2828
import geotrellis.raster.mapalgebra.local
@@ -59,16 +59,16 @@ case class LocalMeanAggregate(child: Expression) extends UnaryRasterAggregate {
5959
override lazy val updateExpressions: Seq[Expression] = Seq(
6060
If(IsNull(count),
6161
SetCellType(Defined(child), Literal("int32")),
62-
If(IsNull(child), count, AddTiles(count, Defined(child)))
62+
If(IsNull(child), count, BiasedAdd(count, Defined(child)))
6363
),
6464
If(IsNull(sum),
6565
SetCellType(child, Literal("float64")),
66-
If(IsNull(child), sum, AddTiles(sum, child))
66+
If(IsNull(child), sum, BiasedAdd(sum, child))
6767
)
6868
)
6969
override val mergeExpressions: Seq[Expression] = Seq(
70-
AddTiles(count.left, count.right),
71-
AddTiles(sum.left, sum.right)
70+
BiasedAdd(count.left, count.right),
71+
BiasedAdd(sum.left, sum.right)
7272
)
7373
override lazy val evaluateExpression: Expression = DivideTiles(sum, count)
7474
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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.aggregates
23+
24+
import org.locationtech.rasterframes._
25+
import org.locationtech.rasterframes.encoders.CatalystSerializer
26+
import org.locationtech.rasterframes.encoders.CatalystSerializer._
27+
import org.locationtech.rasterframes.model.TileDimensions
28+
import geotrellis.proj4.{CRS, Transform}
29+
import geotrellis.raster._
30+
import geotrellis.raster.reproject.{Reproject, ReprojectRasterExtent}
31+
import geotrellis.spark.tiling.LayoutDefinition
32+
import geotrellis.spark.{KeyBounds, SpatialKey, TileLayerMetadata}
33+
import geotrellis.vector.Extent
34+
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
35+
import org.apache.spark.sql.types.{DataType, StructField, StructType}
36+
import org.apache.spark.sql.{Column, Row, TypedColumn}
37+
38+
class ProjectedLayerMetadataAggregate(destCRS: CRS, destDims: TileDimensions) extends UserDefinedAggregateFunction {
39+
import ProjectedLayerMetadataAggregate._
40+
41+
override def inputSchema: StructType = CatalystSerializer[InputRecord].schema
42+
43+
override def bufferSchema: StructType = CatalystSerializer[BufferRecord].schema
44+
45+
override def dataType: DataType = CatalystSerializer[TileLayerMetadata[SpatialKey]].schema
46+
47+
override def deterministic: Boolean = true
48+
49+
override def initialize(buffer: MutableAggregationBuffer): Unit = ()
50+
51+
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
52+
if(!input.isNullAt(0)) {
53+
val in = input.to[InputRecord]
54+
55+
if(buffer.isNullAt(0)) {
56+
in.toBufferRecord(destCRS).write(buffer)
57+
}
58+
else {
59+
val br = buffer.to[BufferRecord]
60+
br.merge(in.toBufferRecord(destCRS)).write(buffer)
61+
}
62+
}
63+
}
64+
65+
override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
66+
(buffer1.isNullAt(0), buffer2.isNullAt(0)) match {
67+
case (false, false)
68+
val left = buffer1.to[BufferRecord]
69+
val right = buffer2.to[BufferRecord]
70+
left.merge(right).write(buffer1)
71+
case (true, false) buffer2.to[BufferRecord].write(buffer1)
72+
case _ ()
73+
}
74+
}
75+
76+
override def evaluate(buffer: Row): Any = {
77+
import org.locationtech.rasterframes.encoders.CatalystSerializer._
78+
val buf = buffer.to[BufferRecord]
79+
80+
val re = RasterExtent(buf.extent, buf.cellSize)
81+
val layout = LayoutDefinition(re, destDims.cols, destDims.rows)
82+
83+
val kb = KeyBounds(layout.mapTransform(buf.extent))
84+
TileLayerMetadata(buf.cellType, layout, buf.extent, destCRS, kb).toRow
85+
}
86+
}
87+
88+
object ProjectedLayerMetadataAggregate {
89+
import org.locationtech.rasterframes.encoders.StandardEncoders._
90+
91+
/** Primary user facing constructor */
92+
def apply(destCRS: CRS, extent: Column, crs: Column, cellType: Column, tileSize: Column): TypedColumn[Any, TileLayerMetadata[SpatialKey]] =
93+
// Ordering must match InputRecord schema
94+
new ProjectedLayerMetadataAggregate(destCRS, TileDimensions(NOMINAL_TILE_SIZE, NOMINAL_TILE_SIZE))(extent, crs, cellType, tileSize).as[TileLayerMetadata[SpatialKey]]
95+
96+
def apply(destCRS: CRS, destDims: TileDimensions, extent: Column, crs: Column, cellType: Column, tileSize: Column): TypedColumn[Any, TileLayerMetadata[SpatialKey]] =
97+
// Ordering must match InputRecord schema
98+
new ProjectedLayerMetadataAggregate(destCRS, destDims)(extent, crs, cellType, tileSize).as[TileLayerMetadata[SpatialKey]]
99+
100+
private[expressions]
101+
case class InputRecord(extent: Extent, crs: CRS, cellType: CellType, tileSize: TileDimensions) {
102+
def toBufferRecord(destCRS: CRS): BufferRecord = {
103+
val transform = Transform(crs, destCRS)
104+
105+
val re = ReprojectRasterExtent(
106+
RasterExtent(extent, tileSize.cols, tileSize.rows),
107+
transform, Reproject.Options.DEFAULT
108+
)
109+
110+
BufferRecord(
111+
re.extent,
112+
cellType,
113+
re.cellSize
114+
)
115+
}
116+
}
117+
118+
private[expressions]
119+
object InputRecord {
120+
implicit val serializer: CatalystSerializer[InputRecord] = new CatalystSerializer[InputRecord]{
121+
override def schema: StructType = StructType(Seq(
122+
StructField("extent", CatalystSerializer[Extent].schema, false),
123+
StructField("crs", CatalystSerializer[CRS].schema, false),
124+
StructField("cellType", CatalystSerializer[CellType].schema, false),
125+
StructField("tileSize", CatalystSerializer[TileDimensions].schema, false)
126+
))
127+
128+
override protected def to[R](t: InputRecord, io: CatalystIO[R]): R =
129+
throw new IllegalStateException("InputRecord is input only.")
130+
131+
override protected def from[R](t: R, io: CatalystIO[R]): InputRecord = InputRecord(
132+
io.get[Extent](t, 0),
133+
io.get[CRS](t, 1),
134+
io.get[CellType](t, 2),
135+
io.get[TileDimensions](t, 3)
136+
)
137+
}
138+
}
139+
140+
private[expressions]
141+
case class BufferRecord(extent: Extent, cellType: CellType, cellSize: CellSize) {
142+
def merge(that: BufferRecord): BufferRecord = {
143+
val ext = this.extent.combine(that.extent)
144+
val ct = this.cellType.union(that.cellType)
145+
val cs = if (this.cellSize.resolution < that.cellSize.resolution) this.cellSize else that.cellSize
146+
BufferRecord(ext, ct, cs)
147+
}
148+
149+
def write(buffer: MutableAggregationBuffer): Unit = {
150+
val encoded = (this).toRow
151+
for(i <- 0 until encoded.size) {
152+
buffer(i) = encoded(i)
153+
}
154+
}
155+
}
156+
157+
private[expressions]
158+
object BufferRecord {
159+
implicit val serializer: CatalystSerializer[BufferRecord] = new CatalystSerializer[BufferRecord] {
160+
override def schema: StructType = StructType(Seq(
161+
StructField("extent", CatalystSerializer[Extent].schema, true),
162+
StructField("cellType", CatalystSerializer[CellType].schema, true),
163+
StructField("cellSize", CatalystSerializer[CellSize].schema, true)
164+
))
165+
166+
override protected def to[R](t: BufferRecord, io: CatalystIO[R]): R = io.create(
167+
io.to(t.extent),
168+
io.to(t.cellType),
169+
io.to(t.cellSize)
170+
)
171+
172+
override protected def from[R](t: R, io: CatalystIO[R]): BufferRecord = BufferRecord(
173+
io.get[Extent](t, 0),
174+
io.get[CellType](t, 1),
175+
io.get[CellSize](t, 2)
176+
)
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)