Skip to content

Commit 9357c56

Browse files
committed
Merge remote-tracking branch 'lt/develop' into feature/unit-test-163
2 parents 563e67b + da0d0f4 commit 9357c56

File tree

26 files changed

+348
-78
lines changed

26 files changed

+348
-78
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/scala/org/locationtech/rasterframes/expressions/aggregates/ProjectedLayerMetadataAggregate.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import org.locationtech.rasterframes.model.TileDimensions
2828
import geotrellis.proj4.{CRS, Transform}
2929
import geotrellis.raster._
3030
import geotrellis.raster.reproject.{Reproject, ReprojectRasterExtent}
31-
import geotrellis.spark.tiling.{FloatingLayoutScheme, LayoutLevel}
31+
import geotrellis.spark.tiling.LayoutDefinition
3232
import geotrellis.spark.{KeyBounds, SpatialKey, TileLayerMetadata}
3333
import geotrellis.vector.Extent
3434
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
@@ -76,7 +76,10 @@ class ProjectedLayerMetadataAggregate(destCRS: CRS, destDims: TileDimensions) ex
7676
override def evaluate(buffer: Row): Any = {
7777
import org.locationtech.rasterframes.encoders.CatalystSerializer._
7878
val buf = buffer.to[BufferRecord]
79-
val LayoutLevel(_, layout) = FloatingLayoutScheme(destDims.cols, destDims.rows).levelFor(buf.extent, buf.cellSize)
79+
80+
val re = RasterExtent(buf.extent, buf.cellSize)
81+
val layout = LayoutDefinition(re, destDims.cols, destDims.rows)
82+
8083
val kb = KeyBounds(layout.mapTransform(buf.extent))
8184
TileLayerMetadata(buf.cellType, layout, buf.extent, destCRS, kb).toRow
8285
}

core/src/main/scala/org/locationtech/rasterframes/ref/GDALRasterSource.scala

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ package org.locationtech.rasterframes.ref
2323

2424
import java.net.URI
2525

26+
import com.azavea.gdal.GDALWarp
27+
import com.typesafe.scalalogging.LazyLogging
28+
import geotrellis.contrib.vlm.gdal.{GDALRasterSource => VLMRasterSource}
2629
import geotrellis.proj4.CRS
27-
import geotrellis.raster.{CellType, GridBounds, MultibandTile, Raster}
2830
import geotrellis.raster.io.geotiff.Tags
31+
import geotrellis.raster.{CellType, GridBounds, MultibandTile, Raster}
2932
import geotrellis.vector.Extent
3033
import org.locationtech.rasterframes.ref.RasterSource.URIRasterSource
31-
import geotrellis.contrib.vlm.gdal.{GDALRasterSource => VLMRasterSource}
3234

3335
case class GDALRasterSource(source: URI) extends RasterSource with URIRasterSource {
3436

@@ -67,3 +69,17 @@ case class GDALRasterSource(source: URI) extends RasterSource with URIRasterSour
6769
override protected def readBounds(bounds: Traversable[GridBounds], bands: Seq[Int]): Iterator[Raster[MultibandTile]] =
6870
gdal.readBounds(bounds, bands)
6971
}
72+
73+
object GDALRasterSource extends LazyLogging {
74+
def gdalVersion(): String = if (hasGDAL) GDALWarp.get_version_info("--version").trim else "not available"
75+
76+
@transient
77+
lazy val hasGDAL: Boolean = try {
78+
val _ = new GDALWarp()
79+
true
80+
} catch {
81+
case _: UnsatisfiedLinkError =>
82+
logger.warn("GDAL native bindings are not available. Falling back to JVM-based reader for GeoTIFF format.")
83+
false
84+
}
85+
}

core/src/main/scala/org/locationtech/rasterframes/ref/RasterSource.scala

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ package org.locationtech.rasterframes.ref
2323

2424
import java.net.URI
2525

26-
import com.azavea.gdal.GDALWarp
2726
import com.github.blemale.scaffeine.Scaffeine
2827
import com.typesafe.scalalogging.LazyLogging
2928
import geotrellis.proj4.CRS
@@ -120,27 +119,18 @@ object RasterSource extends LazyLogging {
120119

121120
/** Determine if we should prefer GDAL for all types. */
122121
private val preferGdal: Boolean = org.locationtech.rasterframes.rfConfig.getBoolean("prefer-gdal")
123-
@transient
124-
lazy val hasGDAL: Boolean = try {
125-
val _ = new GDALWarp()
126-
true
127-
} catch {
128-
case _: UnsatisfiedLinkError =>
129-
logger.warn("GDAL native bindings are not available. Falling back to JVM-based reader.")
130-
false
131-
}
132122

133-
val gdalOnlyExtensions = Seq(".jp2", ".mrf", ".hdf")
123+
val gdalOnlyExtensions = Seq(".jp2", ".mrf", ".hdf", ".vrt")
134124

135125
def gdalOnly(source: URI): Boolean =
136126
if (gdalOnlyExtensions.exists(source.getPath.toLowerCase.endsWith)) {
137-
require(hasGDAL, s"Can only read $source if GDAL is available")
127+
require(GDALRasterSource.hasGDAL, s"Can only read $source if GDAL is available")
138128
true
139129
} else false
140130

141131
/** Extractor for determining if a scheme indicates GDAL preference. */
142132
def unapply(source: URI): Boolean =
143-
gdalOnly(source) || ((preferGdal || source.getScheme.startsWith("gdal")) && hasGDAL)
133+
gdalOnly(source) || ((preferGdal || source.getScheme.startsWith("gdal")) && GDALRasterSource.hasGDAL)
144134
}
145135

146136
object IsDefaultGeoTiff {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import geotrellis.proj4.LatLng
2525
import geotrellis.raster.{ByteCellType, GridBounds, TileLayout}
2626
import geotrellis.spark.tiling.{CRSWorldExtent, LayoutDefinition}
2727
import geotrellis.spark.{KeyBounds, SpatialKey, TileLayerMetadata}
28-
import org.apache.spark.sql.{Encoder, Encoders}
28+
import org.apache.spark.sql.Encoders
2929
import org.locationtech.rasterframes.util.SubdivideSupport
3030

3131
/**

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ trait TestData {
146146

147147
lazy val localSentinel: URI = getClass.getResource("/B01.jp2").toURI
148148
lazy val cogPath: URI = getClass.getResource("/LC08_RGB_Norfolk_COG.tiff").toURI
149+
lazy val singlebandCogPath: URI = getClass.getResource("/LC08_B7_Memphis_COG.tiff").toURI
149150
lazy val nonCogPath: URI = getClass.getResource("/L8-B8-Robinson-IL.tiff").toURI
150151

151152
lazy val l8B1SamplePath: URI = l8SamplePath(1)

core/src/test/scala/org/locationtech/rasterframes/ref/RasterSourceSpec.scala

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,7 @@ import org.apache.spark.sql.rf.RasterSourceUDT
2929
import org.locationtech.rasterframes.TestEnvironment
3030
import org.locationtech.rasterframes.model.TileDimensions
3131

32-
/**
33-
*
34-
*
35-
* @since 8/22/18
36-
*/
32+
3733
class RasterSourceSpec extends TestEnvironment with TestData {
3834
def sub(e: Extent) = {
3935
val c = e.center
@@ -112,7 +108,7 @@ class RasterSourceSpec extends TestEnvironment with TestData {
112108
}
113109
}
114110

115-
if(RasterSource.IsGDAL.hasGDAL) {
111+
if(GDALRasterSource.hasGDAL) {
116112
describe("GDAL Rastersource") {
117113
val gdal = GDALRasterSource(cogPath)
118114
val jvm = JVMGeoTiffRasterSource(cogPath)

datasource/src/main/scala/org/locationtech/rasterframes/datasource/geotiff/GeoTiffDataSource.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ class GeoTiffDataSource
121121
rf_dimensions(tileCol)
122122
))
123123
.first()
124+
logger.debug(s"Contructed TileLayerMetadata: ${tlm.toString}")
124125

125126
val c = ProjectedRasterDefinition(tlm)
126127

datasource/src/test/scala/org/locationtech/rasterframes/datasource/geotiff/GeoTiffDataSourceSpec.scala

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ package org.locationtech.rasterframes.datasource.geotiff
2323
import java.nio.file.Paths
2424

2525
import geotrellis.proj4._
26+
import geotrellis.raster.ProjectedRaster
27+
import geotrellis.raster.io.geotiff.{MultibandGeoTiff, SinglebandGeoTiff}
28+
import geotrellis.vector.Extent
2629
import org.locationtech.rasterframes._
2730
import org.apache.spark.sql.functions._
2831
import org.locationtech.rasterframes.TestEnvironment
@@ -41,7 +44,7 @@ class GeoTiffDataSourceSpec
4144
assert(rf.count() > 10)
4245
}
4346

44-
it("should lay out tiles correctly"){
47+
it("should lay out tiles correctly") {
4548

4649
val rf = spark.read.format("geotiff").load(cogPath.toASCIIString).asLayer
4750

@@ -83,6 +86,9 @@ class GeoTiffDataSourceSpec
8386

8487
assert(result === expected)
8588
}
89+
}
90+
91+
describe("GeoTiff writing") {
8692

8793
it("should write GeoTIFF RF to parquet") {
8894
val rf = spark.read.format("geotiff").load(cogPath.toASCIIString).asLayer
@@ -101,6 +107,81 @@ class GeoTiffDataSourceSpec
101107
}
102108
}
103109

110+
it("should write unstructured raster") {
111+
import spark.implicits._
112+
val df = spark.read.format("raster")
113+
.option("tileDimensions", "32,32") // oddball
114+
.load(nonCogPath.toASCIIString) // core L8-B8-Robinson-IL.tiff
115+
116+
df.count() should be > 0L
117+
118+
val crs = df.select(rf_crs($"proj_raster")).first()
119+
120+
noException shouldBe thrownBy {
121+
df.write.geotiff.withCRS(crs).save("unstructured.tif")
122+
}
123+
124+
val (inCols, inRows) = {
125+
val id = sampleGeoTiff.imageData // inshallah same as nonCogPath
126+
(id.cols, id.rows)
127+
}
128+
inCols should be (774)
129+
inRows should be (500) //from gdalinfo
130+
131+
val outputTif = SinglebandGeoTiff("unstructured.tif")
132+
outputTif.imageData.cols should be (inCols)
133+
outputTif.imageData.rows should be (inRows)
134+
135+
// TODO check datatype, extent.
136+
}
137+
138+
it("should round trip unstructured raster from COG"){
139+
import spark.implicits._
140+
import org.locationtech.rasterframes.datasource.raster._
141+
142+
val df = spark.read.raster.withTileDimensions(64, 64).load(singlebandCogPath.toASCIIString)
143+
144+
val resourceCols = 963 // from gdalinfo
145+
val resourceRows = 754
146+
val resourceExtent = Extent(752325.0, 3872685.0, 781215.0, 3895305.0)
147+
148+
df.count() should be > 0L
149+
150+
val crs = df.select(rf_crs(col("proj_raster"))).first()
151+
152+
val totalExtentRow = df.select(rf_extent($"proj_raster").alias("ext"))
153+
.agg(
154+
min($"ext.xmin").alias("xmin"),
155+
min($"ext.ymin").alias("ymin"),
156+
max($"ext.xmax").alias("xmax"),
157+
max($"ext.ymax").alias("ymax")
158+
).first()
159+
val dfExtent = Extent(totalExtentRow.getDouble(0), totalExtentRow.getDouble(1), totalExtentRow.getDouble(2), totalExtentRow.getDouble(3))
160+
logger.info(s"Dataframe extent: ${dfExtent.toString()}")
161+
162+
dfExtent shouldBe (resourceExtent)
163+
164+
noException shouldBe thrownBy {
165+
df.write.geotiff.withCRS(crs).save("target/unstructured_cog.tif")
166+
}
167+
168+
val (inCols, inRows, inExtent, inCellType) = {
169+
val tif = readSingleband("LC08_B7_Memphis_COG.tiff")
170+
val id = tif.imageData
171+
(id.cols, id.rows, tif.extent, tif.cellType)
172+
}
173+
inCols should be (963)
174+
inRows should be (754) //from gdalinfo
175+
inExtent should be (resourceExtent)
176+
177+
val outputTif = SinglebandGeoTiff("target/unstructured_cog.tif")
178+
outputTif.imageData.cols should be (inCols)
179+
outputTif.imageData.rows should be (inRows)
180+
outputTif.extent should be (resourceExtent)
181+
outputTif.cellType should be (inCellType)
182+
183+
}
184+
104185
it("should write GeoTIFF without layer") {
105186
import org.locationtech.rasterframes.datasource.raster._
106187
val pr = col("proj_raster_b0")
@@ -152,6 +233,8 @@ ${s(1)},${s(4)},${s(3)}
152233
.withDimensions(256, 256)
153234
.save("geotiff-overview.tif")
154235

236+
val outTif = MultibandGeoTiff("geotiff-overview.tif")
237+
outTif.bandCount should be (3)
155238
}
156239
}
157240
}

0 commit comments

Comments
 (0)