Skip to content

Commit 562e962

Browse files
authored
Merge pull request #231 from s22s/fix/gtiff-distort-224
Refactor construction of LayoutDefinition in GeoTiff writing process
2 parents 38b8c2d + 2888dd4 commit 562e962

File tree

8 files changed

+141
-10
lines changed

8 files changed

+141
-10
lines changed

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/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)

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
}

datasource/src/test/scala/org/locationtech/rasterframes/datasource/raster/RasterSourceDataSourceSpec.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,41 @@ class RasterSourceDataSourceSpec extends TestEnvironment with TestData {
245245

246246
}
247247
}
248+
249+
describe("RasterSource breaks up scenes into tiles") {
250+
val modis_df = spark.read.raster
251+
.withTileDimensions(128, 128)
252+
.withLazyTiles(true)
253+
.load(remoteMODIS.toASCIIString())
254+
255+
val l8_df = spark.read.raster
256+
.withTileDimensions(32, 33)
257+
.withLazyTiles(true)
258+
.load(remoteL8.toASCIIString())
259+
260+
ignore("should have at most four tile dimensions reading MODIS; ignore until fix #242") {
261+
val dims = modis_df.select(rf_dimensions($"proj_raster")).distinct().collect()
262+
dims.length should be > (0)
263+
dims.length should be <= (4)
264+
}
265+
266+
it("should have at most four tile dimensions reading landsat") {
267+
val dims = l8_df.select(rf_dimensions($"proj_raster")).distinct().collect()
268+
dims.length should be > (0)
269+
dims.length should be <= (4)
270+
}
271+
272+
ignore("should have consistent tile resolution reading MODIS; ignore until fix #242") {
273+
val res = modis_df.select((rf_extent($"proj_raster").getField("xmax") - rf_extent($"proj_raster").getField("xmin")) /
274+
rf_dimensions($"proj_raster").getField("cols")).distinct().collect()
275+
res.length should be (1)
276+
}
277+
278+
it("should have consistent tile resolution reading Landsat") {
279+
val res = l8_df.select((rf_extent($"proj_raster").getField("xmax") - rf_extent($"proj_raster").getField("xmin")) /
280+
rf_dimensions($"proj_raster").getField("cols")).distinct().collect()
281+
res.length should be (1)
282+
}
283+
284+
}
248285
}

pyrasterframes/src/main/python/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def initialize_options(self):
153153
'shapely',
154154
'pandas',
155155
'rasterio',
156-
'boto3'
156+
'boto3',
157157
],
158158
packages=[
159159
'pyrasterframes',

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class GeoTiffWriter(TestEnvironment):
2929

3030
@staticmethod
3131
def _tmpfile():
32-
return os.path.join(tempfile.tempdir, "pyrf-test.tif")
32+
return os.path.join(tempfile.gettempdir(), "pyrf-test.tif")
3333

3434
def test_identity_write(self):
3535
rf = self.spark.read.geotiff(self.img_uri)
@@ -44,13 +44,19 @@ def test_identity_write(self):
4444

4545
def test_unstructured_write(self):
4646
rf = self.spark.read.raster(self.img_uri)
47-
dest = self._tmpfile()
48-
rf.write.geotiff(dest, crs='EPSG:32616')
47+
dest_file = self._tmpfile()
48+
rf.write.geotiff(dest_file, crs='EPSG:32616')
4949

50-
rf2 = self.spark.read.raster('file://' + dest)
50+
rf2 = self.spark.read.raster('file://' + dest_file)
5151
self.assertEqual(rf2.count(), rf.count())
5252

53-
os.remove(dest)
53+
with rasterio.open(self.img_uri) as source:
54+
with rasterio.open(dest_file) as dest:
55+
self.assertEqual((dest.width, dest.height), (source.width, source.height))
56+
self.assertEqual(dest.bounds, source.bounds)
57+
self.assertEqual(dest.crs, source.crs)
58+
59+
os.remove(dest_file)
5460

5561
def test_downsampled_write(self):
5662
rf = self.spark.read.raster(self.img_uri)

0 commit comments

Comments
 (0)