Skip to content

Commit 86b64ed

Browse files
authored
Merge pull request #212 from s22s/docs/raster-writing-1
Writing GeoTIFFs
2 parents b1a753d + 4201692 commit 86b64ed

37 files changed

+889
-696
lines changed

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: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ package org.locationtech.rasterframes.expressions
2424
import geotrellis.raster.{CellGrid, Tile}
2525
import org.apache.spark.sql.Row
2626
import org.apache.spark.sql.catalyst.InternalRow
27-
import org.apache.spark.sql.rf.{TileUDT, _}
27+
import org.apache.spark.sql.rf.{TileUDT, RasterSourceUDT}
2828
import org.apache.spark.sql.types._
2929
import org.locationtech.rasterframes.encoders.CatalystSerializer._
3030
import org.locationtech.rasterframes.model.TileContext
@@ -38,7 +38,7 @@ object DynamicExtractors {
3838
case _: TileUDT =>
3939
(row: InternalRow) =>
4040
(row.to[Tile](TileUDT.tileSerializer), None)
41-
case t if t.conformsTo(schemaOf[ProjectedRasterTile]) =>
41+
case t if t.conformsTo[ProjectedRasterTile] =>
4242
(row: InternalRow) => {
4343
val prt = row.to[ProjectedRasterTile]
4444
(prt, Some(TileContext(prt)))
@@ -48,7 +48,7 @@ object DynamicExtractors {
4848
lazy val rowTileExtractor: PartialFunction[DataType, Row => (Tile, Option[TileContext])] = {
4949
case _: TileUDT =>
5050
(row: Row) => (row.to[Tile](TileUDT.tileSerializer), None)
51-
case t if t.conformsTo(schemaOf[ProjectedRasterTile]) =>
51+
case t if t.conformsTo[ProjectedRasterTile] =>
5252
(row: Row) => {
5353
val prt = row.to[ProjectedRasterTile]
5454
(prt, Some(TileContext(prt)))
@@ -59,9 +59,9 @@ object DynamicExtractors {
5959
lazy val projectedRasterLikeExtractor: PartialFunction[DataType, InternalRow ProjectedRasterLike] = {
6060
case _: RasterSourceUDT
6161
(row: InternalRow) => row.to[RasterSource](RasterSourceUDT.rasterSourceSerializer)
62-
case t if t.conformsTo(schemaOf[ProjectedRasterTile]) =>
62+
case t if t.conformsTo[ProjectedRasterTile] =>
6363
(row: InternalRow) => row.to[ProjectedRasterTile]
64-
case t if t.conformsTo(schemaOf[RasterRef]) =>
64+
case t if t.conformsTo[RasterRef] =>
6565
(row: InternalRow) => row.to[RasterRef]
6666
}
6767

@@ -71,9 +71,9 @@ object DynamicExtractors {
7171
(row: InternalRow) => row.to[Tile](TileUDT.tileSerializer)
7272
case _: RasterSourceUDT =>
7373
(row: InternalRow) => row.to[RasterSource](RasterSourceUDT.rasterSourceSerializer)
74-
case t if t.conformsTo(schemaOf[RasterRef])
74+
case t if t.conformsTo[RasterRef]
7575
(row: InternalRow) => row.to[RasterRef]
76-
case t if t.conformsTo(schemaOf[ProjectedRasterTile]) =>
76+
case t if t.conformsTo[ProjectedRasterTile] =>
7777
(row: InternalRow) => row.to[ProjectedRasterTile]
7878
}
7979

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
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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.{FloatingLayoutScheme, LayoutLevel}
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+
val LayoutLevel(_, layout) = FloatingLayoutScheme(destDims.cols, destDims.rows).levelFor(buf.extent, buf.cellSize)
80+
val kb = KeyBounds(layout.mapTransform(buf.extent))
81+
TileLayerMetadata(buf.cellType, layout, buf.extent, destCRS, kb).toRow
82+
}
83+
}
84+
85+
object ProjectedLayerMetadataAggregate {
86+
import org.locationtech.rasterframes.encoders.StandardEncoders._
87+
88+
/** Primary user facing constructor */
89+
def apply(destCRS: CRS, extent: Column, crs: Column, cellType: Column, tileSize: Column): TypedColumn[Any, TileLayerMetadata[SpatialKey]] =
90+
// Ordering must match InputRecord schema
91+
new ProjectedLayerMetadataAggregate(destCRS, TileDimensions(NOMINAL_TILE_SIZE, NOMINAL_TILE_SIZE))(extent, crs, cellType, tileSize).as[TileLayerMetadata[SpatialKey]]
92+
93+
def apply(destCRS: CRS, destDims: TileDimensions, extent: Column, crs: Column, cellType: Column, tileSize: Column): TypedColumn[Any, TileLayerMetadata[SpatialKey]] =
94+
// Ordering must match InputRecord schema
95+
new ProjectedLayerMetadataAggregate(destCRS, destDims)(extent, crs, cellType, tileSize).as[TileLayerMetadata[SpatialKey]]
96+
97+
private[expressions]
98+
case class InputRecord(extent: Extent, crs: CRS, cellType: CellType, tileSize: TileDimensions) {
99+
def toBufferRecord(destCRS: CRS): BufferRecord = {
100+
val transform = Transform(crs, destCRS)
101+
102+
val re = ReprojectRasterExtent(
103+
RasterExtent(extent, tileSize.cols, tileSize.rows),
104+
transform, Reproject.Options.DEFAULT
105+
)
106+
107+
BufferRecord(
108+
re.extent,
109+
cellType,
110+
re.cellSize
111+
)
112+
}
113+
}
114+
115+
private[expressions]
116+
object InputRecord {
117+
implicit val serializer: CatalystSerializer[InputRecord] = new CatalystSerializer[InputRecord]{
118+
override def schema: StructType = StructType(Seq(
119+
StructField("extent", CatalystSerializer[Extent].schema, false),
120+
StructField("crs", CatalystSerializer[CRS].schema, false),
121+
StructField("cellType", CatalystSerializer[CellType].schema, false),
122+
StructField("tileSize", CatalystSerializer[TileDimensions].schema, false)
123+
))
124+
125+
override protected def to[R](t: InputRecord, io: CatalystIO[R]): R =
126+
throw new IllegalStateException("InputRecord is input only.")
127+
128+
override protected def from[R](t: R, io: CatalystIO[R]): InputRecord = InputRecord(
129+
io.get[Extent](t, 0),
130+
io.get[CRS](t, 1),
131+
io.get[CellType](t, 2),
132+
io.get[TileDimensions](t, 3)
133+
)
134+
}
135+
}
136+
137+
private[expressions]
138+
case class BufferRecord(extent: Extent, cellType: CellType, cellSize: CellSize) {
139+
def merge(that: BufferRecord): BufferRecord = {
140+
val ext = this.extent.combine(that.extent)
141+
val ct = this.cellType.union(that.cellType)
142+
val cs = if (this.cellSize.resolution < that.cellSize.resolution) this.cellSize else that.cellSize
143+
BufferRecord(ext, ct, cs)
144+
}
145+
146+
def write(buffer: MutableAggregationBuffer): Unit = {
147+
val encoded = (this).toRow
148+
for(i <- 0 until encoded.size) {
149+
buffer(i) = encoded(i)
150+
}
151+
}
152+
}
153+
154+
private[expressions]
155+
object BufferRecord {
156+
implicit val serializer: CatalystSerializer[BufferRecord] = new CatalystSerializer[BufferRecord] {
157+
override def schema: StructType = StructType(Seq(
158+
StructField("extent", CatalystSerializer[Extent].schema, true),
159+
StructField("cellType", CatalystSerializer[CellType].schema, true),
160+
StructField("cellSize", CatalystSerializer[CellSize].schema, true)
161+
))
162+
163+
override protected def to[R](t: BufferRecord, io: CatalystIO[R]): R = io.create(
164+
io.to(t.extent),
165+
io.to(t.cellType),
166+
io.to(t.cellSize)
167+
)
168+
169+
override protected def from[R](t: R, io: CatalystIO[R]): BufferRecord = BufferRecord(
170+
io.get[Extent](t, 0),
171+
io.get[CellType](t, 1),
172+
io.get[CellSize](t, 2)
173+
)
174+
}
175+
}
176+
}

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,17 @@
2121

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

24-
import org.locationtech.rasterframes._
25-
import org.locationtech.rasterframes.encoders.CatalystSerializer._
2624
import geotrellis.proj4.CRS
25+
import geotrellis.raster.reproject.Reproject
2726
import geotrellis.raster.resample.ResampleMethod
2827
import geotrellis.raster.{ArrayTile, CellType, Raster, Tile}
28+
import geotrellis.spark.TileLayerMetadata
2929
import geotrellis.vector.Extent
3030
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
3131
import org.apache.spark.sql.types.{DataType, StructField, StructType}
3232
import org.apache.spark.sql.{Column, Row, TypedColumn}
33-
import geotrellis.raster.reproject.Reproject
33+
import org.locationtech.rasterframes._
34+
import org.locationtech.rasterframes.encoders.CatalystSerializer._
3435
import org.locationtech.rasterframes.expressions.aggregates.TileRasterizerAggregate.ProjectedRasterDefinition
3536

3637
/**
@@ -57,7 +58,7 @@ class TileRasterizerAggregate(prd: ProjectedRasterDefinition) extends UserDefine
5758
override def dataType: DataType = schemaOf[Raster[Tile]]
5859

5960
override def initialize(buffer: MutableAggregationBuffer): Unit = {
60-
buffer(0) = ArrayTile.empty(prd.cellType, prd.cols, prd.rows)
61+
buffer(0) = ArrayTile.empty(prd.cellType, prd.totalCols, prd.totalRows)
6162
}
6263

6364
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
@@ -87,9 +88,21 @@ class TileRasterizerAggregate(prd: ProjectedRasterDefinition) extends UserDefine
8788
}
8889

8990
object TileRasterizerAggregate {
90-
val nodeName = "tile_rasterizer_aggregate"
91+
val nodeName = "rf_tile_rasterizer_aggregate"
9192
/** Convenience grouping of parameters needed for running aggregate. */
92-
case class ProjectedRasterDefinition(cols: Int, rows: Int, cellType: CellType, crs: CRS, extent: Extent, sampler: ResampleMethod = ResampleMethod.DEFAULT)
93+
case class ProjectedRasterDefinition(totalCols: Int, totalRows: Int, cellType: CellType, crs: CRS, extent: Extent, sampler: ResampleMethod = ResampleMethod.DEFAULT)
94+
95+
object ProjectedRasterDefinition {
96+
def apply(tlm: TileLayerMetadata[_]): ProjectedRasterDefinition = apply(tlm, ResampleMethod.DEFAULT)
97+
98+
def apply(tlm: TileLayerMetadata[_], sampler: ResampleMethod): ProjectedRasterDefinition = {
99+
// Try to determine the actual dimensions of our data coverage
100+
val actualSize = tlm.layout.toRasterExtent().gridBoundsFor(tlm.extent) // <--- Do we have the math right here?
101+
val cols = actualSize.width
102+
val rows = actualSize.height
103+
new ProjectedRasterDefinition(cols, rows, tlm.cellType, tlm.crs, tlm.extent, sampler)
104+
}
105+
}
93106

94107
def apply(prd: ProjectedRasterDefinition, crsCol: Column, extentCol: Column, tileCol: Column): TypedColumn[Any, Raster[Tile]] =
95108
new TileRasterizerAggregate(prd)(crsCol, extentCol, tileCol).as(nodeName).as[Raster[Tile]]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.{TypeCheckFailure,
3232
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
3333
import org.apache.spark.sql.catalyst.expressions.{BinaryExpression, Expression}
3434
import org.apache.spark.sql.functions.lit
35-
import org.apache.spark.sql.rf._
35+
import org.apache.spark.sql.rf.{TileUDT, WithTypeConformity}
3636
import org.apache.spark.sql.types._
3737
import org.apache.spark.sql.{Column, TypedColumn}
3838
import org.apache.spark.unsafe.types.UTF8String

0 commit comments

Comments
 (0)