Skip to content

Commit 84985ad

Browse files
committed
PR feedback.
1 parent 2c6df24 commit 84985ad

File tree

13 files changed

+103
-90
lines changed

13 files changed

+103
-90
lines changed

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ import org.locationtech.jts.geom._
3333
class ReprojectGeometrySpec extends TestEnvironment {
3434
// Note: Test data copied from ReprojectSpec in GeoTrellis
3535
val fact = new GeometryFactory()
36-
val latLng: Geometry = fact.createLineString(Array(
36+
val llLineString: Geometry = fact.createLineString(Array(
3737
new Coordinate(-111.09374999999999, 34.784483415461345),
3838
new Coordinate(-111.09374999999999, 43.29919735147067),
3939
new Coordinate(-75.322265625, 43.29919735147067),
4040
new Coordinate(-75.322265625, 34.784483415461345),
4141
new Coordinate(-111.09374999999999, 34.784483415461345)
4242
))
4343

44-
val webMercator: Geometry = fact.createLineString(Array(
44+
val wmLineString: Geometry = fact.createLineString(Array(
4545
new Coordinate(-12366899.680315234, 4134631.734001753),
4646
new Coordinate(-12366899.680315234, 5357624.186564572),
4747
new Coordinate(-8384836.254770693, 5357624.186564572),
@@ -54,7 +54,7 @@ class ReprojectGeometrySpec extends TestEnvironment {
5454

5555
it("should handle two literal CRSs") {
5656

57-
val df = Seq((latLng, webMercator)).toDF("ll", "wm")
57+
val df = Seq((llLineString, wmLineString)).toDF("ll", "wm")
5858

5959
val rp = df.select(
6060
st_reproject($"ll", LatLng, WebMercator) as "wm2",
@@ -65,14 +65,14 @@ class ReprojectGeometrySpec extends TestEnvironment {
6565

6666
val (wm2, ll2, wm3) = rp.first()
6767

68-
wm2 should matchGeom(webMercator, 0.00001)
69-
ll2 should matchGeom(latLng, 0.00001)
70-
wm3 should matchGeom(webMercator, 0.00001)
68+
wm2 should matchGeom(wmLineString, 0.00001)
69+
ll2 should matchGeom(llLineString, 0.00001)
70+
wm3 should matchGeom(wmLineString, 0.00001)
7171
}
7272

7373
it("should handle one literal crs") {
7474
implicit val enc = Encoders.tuple(jtsGeometryEncoder, jtsGeometryEncoder, crsEncoder)
75-
val df = Seq((latLng, webMercator, LatLng: CRS)).toDF("ll", "wm", "llCRS")
75+
val df = Seq((llLineString, wmLineString, LatLng: CRS)).toDF("ll", "wm", "llCRS")
7676

7777
val rp = df.select(
7878
st_reproject($"ll", $"llCRS", WebMercator) as "wm2",
@@ -83,9 +83,9 @@ class ReprojectGeometrySpec extends TestEnvironment {
8383

8484
val (wm2, ll2, wm3) = rp.first()
8585

86-
wm2 should matchGeom(webMercator, 0.00001)
87-
ll2 should matchGeom(latLng, 0.00001)
88-
wm3 should matchGeom(webMercator, 0.00001)
86+
wm2 should matchGeom(wmLineString, 0.00001)
87+
ll2 should matchGeom(llLineString, 0.00001)
88+
wm3 should matchGeom(wmLineString, 0.00001)
8989
}
9090

9191
it("should accept other geometry types") {
@@ -98,7 +98,7 @@ class ReprojectGeometrySpec extends TestEnvironment {
9898

9999
it("should work in SQL") {
100100
implicit val enc = Encoders.tuple(jtsGeometryEncoder, jtsGeometryEncoder, crsEncoder)
101-
val df = Seq((latLng, webMercator, LatLng: CRS)).toDF("ll", "wm", "llCRS")
101+
val df = Seq((llLineString, wmLineString, LatLng: CRS)).toDF("ll", "wm", "llCRS")
102102
df.createOrReplaceTempView("geom")
103103

104104
val rp = spark.sql(
@@ -112,9 +112,9 @@ class ReprojectGeometrySpec extends TestEnvironment {
112112

113113
val (wm2, ll2, wm3) = rp.first()
114114

115-
wm2 should matchGeom(webMercator, 0.00001)
116-
ll2 should matchGeom(latLng, 0.00001)
117-
wm3 should matchGeom(webMercator, 0.00001)
115+
wm2 should matchGeom(wmLineString, 0.00001)
116+
ll2 should matchGeom(llLineString, 0.00001)
117+
wm3 should matchGeom(wmLineString, 0.00001)
118118

119119
checkDocs("st_reproject")
120120
}

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

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,10 @@ import org.locationtech.rasterframes.model.{LazyCRS, TileDimensions}
4040
import org.locationtech.rasterframes.util._
4141

4242
/**
43-
* Spark SQL data source over GeoTIFF files.
44-
* @since 1/14/18
43+
* Spark SQL data source over GeoTIFF files.
4544
*/
46-
class GeoTiffDataSource extends DataSourceRegister
47-
with RelationProvider with CreatableRelationProvider
48-
with DataSourceOptions with LazyLogging {
45+
class GeoTiffDataSource
46+
extends DataSourceRegister with RelationProvider with CreatableRelationProvider with DataSourceOptions with LazyLogging {
4947
import GeoTiffDataSource._
5048

5149
def shortName() = GeoTiffDataSource.SHORT_NAME
@@ -56,42 +54,40 @@ class GeoTiffDataSource extends DataSourceRegister
5654

5755
val p = parameters.path.get
5856

59-
if(p.getPath.contains("*")) {
57+
if (p.getPath.contains("*")) {
6058
val bandCount = parameters.get(GeoTiffDataSource.BAND_COUNT_PARAM).map(_.toInt).getOrElse(1)
6159
GeoTiffCollectionRelation(sqlContext, p, bandCount)
62-
}
63-
else GeoTiffRelation(sqlContext, p)
60+
} else GeoTiffRelation(sqlContext, p)
6461
}
6562

6663
override def createRelation(sqlContext: SQLContext, mode: SaveMode, parameters: Map[String, String], df: DataFrame): BaseRelation = {
6764
require(parameters.path.isDefined, "Valid URI 'path' parameter required.")
6865
val path = parameters.path.get
69-
require(path.getScheme == "file" || path.getScheme == null,
70-
"Currently only 'file://' destinations are supported")
66+
require(path.getScheme == "file" || path.getScheme == null, "Currently only 'file://' destinations are supported")
7167
sqlContext.withRasterFrames
7268

7369
val tileCols = df.tileColumns
7470

7571
require(tileCols.nonEmpty, "Could not find any tile columns.")
7672

77-
val raster = if(df.isAlreadyLayer) {
73+
val raster = if (df.isAlreadyLayer) {
7874
val layer = df.certify
7975
val tlm = layer.tileLayerMetadata.merge
8076

8177
// If no desired image size is given, write at full size.
82-
val TileDimensions(cols, rows) = parameters.rasterDimensions
78+
val TileDimensions(cols, rows) = parameters.rasterDimensions
8379
.getOrElse {
8480
val actualSize = tlm.layout.toRasterExtent().gridBoundsFor(tlm.extent)
8581
TileDimensions(actualSize.width, actualSize.height)
8682
}
8783

8884
// Should we really play traffic cop here?
89-
if(cols.toDouble * rows * 64.0 > Runtime.getRuntime.totalMemory() * 0.5)
90-
logger.warn(s"You've asked for the construction of a very large image ($cols x $rows), destined for ${path}. Out of memory error likely.")
85+
if (cols.toDouble * rows * 64.0 > Runtime.getRuntime.totalMemory() * 0.5)
86+
logger.warn(
87+
s"You've asked for the construction of a very large image ($cols x $rows), destined for ${path}. Out of memory error likely.")
9188

9289
layer.toMultibandRaster(tileCols, cols.toInt, rows.toInt)
93-
}
94-
else {
90+
} else {
9591
require(parameters.crs.nonEmpty, "A destination CRS must be provided")
9692
require(tileCols.nonEmpty, "need at least one tile column")
9793

@@ -102,10 +98,9 @@ class GeoTiffDataSource extends DataSourceRegister
10298
val (extCol, crsCol, tileCol) = {
10399
// Favor "ProjectedRaster" columns
104100
val prCols = df.projRasterColumns
105-
if(prCols.nonEmpty) {
101+
if (prCols.nonEmpty) {
106102
(rf_extent(prCols.head), rf_crs(prCols.head), rf_tile(prCols.head))
107-
}
108-
else {
103+
} else {
109104
// If no "ProjectedRaster" column, look for single Extent and CRS columns.
110105
val crsCols = df.crsColumns
111106
require(crsCols.size == 1, "Exactly one CRS column must be in DataFrame")
@@ -117,45 +112,48 @@ class GeoTiffDataSource extends DataSourceRegister
117112

118113
// Scan table and constuct what the TileLayerMetadata would be in the specified destination CRS.
119114
val tlm: TileLayerMetadata[SpatialKey] = df
120-
.select(ProjectedLayerMetadataAggregate(
121-
destCRS, extCol, crsCol, rf_cell_type(tileCol), rf_dimensions(tileCol)
122-
))
115+
.select(
116+
ProjectedLayerMetadataAggregate(
117+
destCRS,
118+
extCol,
119+
crsCol,
120+
rf_cell_type(tileCol),
121+
rf_dimensions(tileCol)
122+
))
123123
.first()
124124

125125
val c = ProjectedRasterDefinition(tlm)
126126

127-
val config = parameters.rasterDimensions.map { dims =>
128-
c.copy(totalCols = dims.cols, totalRows = dims.rows)
129-
}.getOrElse(c)
127+
val config = parameters.rasterDimensions
128+
.map { dims =>
129+
c.copy(totalCols = dims.cols, totalRows = dims.rows)
130+
}
131+
.getOrElse(c)
130132

131133
val aggs = tileCols
132-
.map(t => TileRasterizerAggregate(
133-
config, crsCol, extCol, rf_tile(t))("tile").as(t.columnName)
134-
)
134+
.map(t => TileRasterizerAggregate(config, crsCol, extCol, rf_tile(t))("tile").as(t.columnName))
135135

136136
val agg = df.select(aggs: _*)
137137

138138
val row = agg.first()
139139

140-
val bands = for(i <- 0 until row.size) yield row.getAs[Tile](i)
140+
val bands = for (i <- 0 until row.size) yield row.getAs[Tile](i)
141141

142142
ProjectedRaster(MultibandTile(bands), tlm.extent, tlm.crs)
143143
}
144144

145145
val tags = Tags(
146146
RFBuildInfo.toMap.filter(_._1.startsWith("rf")).mapValues(_.toString),
147-
tileCols.map(c Map("RF_COL" -> c.columnName)).toList
147+
tileCols.map(c => Map("RF_COL" -> c.columnName)).toList
148148
)
149149

150150
// We make some assumptions here.... eventually have column metadata encode this.
151151
val colorSpace = tileCols.size match {
152-
case 3 | 4 ColorSpace.RGB
153-
case _ ColorSpace.BlackIsZero
152+
case 3 | 4 => ColorSpace.RGB
153+
case _ => ColorSpace.BlackIsZero
154154
}
155155

156-
val tiffOptions = GeoTiffOptions(Tiled,
157-
if (parameters.compress) DeflateCompression else NoCompression, colorSpace
158-
)
156+
val tiffOptions = GeoTiffOptions(Tiled, if (parameters.compress) DeflateCompression else NoCompression, colorSpace)
159157

160158
val geotiff = new MultibandGeoTiff(raster.tile, raster.extent, raster.crs, tags, tiffOptions)
161159

@@ -174,19 +172,19 @@ object GeoTiffDataSource {
174172
final val CRS_PARAM = "crs"
175173
final val BAND_COUNT_PARAM = "bandCount"
176174

177-
private[geotiff]
178-
implicit class ParamsDictAccessors(val parameters: Map[String, String]) extends AnyVal {
175+
private[geotiff] implicit class ParamsDictAccessors(val parameters: Map[String, String]) extends AnyVal {
179176
def path: Option[URI] = uriParam(PATH_PARAM, parameters)
180177
def compress: Boolean = parameters.get(COMPRESS_PARAM).exists(_.toBoolean)
181178
def crs: Option[CRS] = parameters.get(CRS_PARAM).map(s => LazyCRS(s))
182179
def rasterDimensions: Option[TileDimensions] = {
183180
numParam(IMAGE_WIDTH_PARAM, parameters)
184181
.zip(numParam(IMAGE_HEIGHT_PARAM, parameters))
185-
.map { case (cols, rows) =>
186-
require(cols <= Int.MaxValue && rows <= Int.MaxValue,
187-
s"Can't construct a GeoTIFF of size $cols x $rows. (Too big!)")
188-
TileDimensions(cols.toInt, rows.toInt)
189-
}.headOption
182+
.map {
183+
case (cols, rows) =>
184+
require(cols <= Int.MaxValue && rows <= Int.MaxValue, s"Can't construct a GeoTIFF of size $cols x $rows. (Too big!)")
185+
TileDimensions(cols.toInt, rows.toInt)
186+
}
187+
.headOption
190188
}
191189
}
192190
}

docs/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ To set up an environment whereby you can easily test/evaluate your code blocks d
6767
sbt:RasterFrames> pyrasterframes/doc
6868
```
6969
There's a command alias for this last step: `pyDocs`.
70-
4. To evaluate a single `.pymd` file, you pass the `-f` option and the filename relative to the `python` directory:
70+
4. To evaluate a single `.pymd` file, you pass the `-s` option and the filename relative to the `python` directory:
7171
```
72-
sbt:RasterFrames> pyrasterframes/pySetup pweave -f docs/getting-started.pymd
72+
sbt:RasterFrames> pyrasterframes/pySetup pweave -s docs/getting-started.pymd
7373
[info] Synchronizing 44 files to '<src-root>/pyrasterframes/target/python'
74-
[info] Running 'python setup.py pweave -f docs/getting-started.pymd' in '<src-root>/pyrasterframes/target/python'
74+
[info] Running 'python setup.py pweave -s docs/getting-started.pymd' in '<src-root>/pyrasterframes/target/python'
7575
running pweave
7676
--------------------------------------------------
7777
Running getting-started
@@ -85,9 +85,9 @@ To set up an environment whereby you can easily test/evaluate your code blocks d
8585
5. The _output_ Markdown files are written to `<src-root>/pyrasterframes/target/python/docs`. _Note_: don't edit any files in the `pyrasterframes/target` directory... they will get overwritten each time `sbt` runs a command.
8686
6. During content development it's sometimes helpful to see the output rendered as basic HTML. To do this, add the `-d html` option to the pweave command:
8787
```
88-
sbt:RasterFrames> pyrasterframes/pySetup pweave -d html -f docs/getting-started.pymd
88+
sbt:RasterFrames> pyrasterframes/pySetup pweave -f html -s docs/getting-started.pymd
8989
[info] Synchronizing 54 files to '<src-roog>/pyrasterframes/target/python'
90-
[info] Running 'python setup.py pweave -d html -f docs/getting-started.pymd' in '<src-root>/pyrasterframes/target/python'
90+
[info] Running 'python setup.py pweave -f html -s docs/getting-started.pymd' in '<src-root>/pyrasterframes/target/python'
9191
running pweave
9292
--------------------------------------------------
9393
Running getting-started

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

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

55
### 0.8.0
66

7+
* Super-duper new Python-centric [RasterFrames Users' Manual](https://rasterframes.io/)!
78
* Upgraded to the following core dependencies: Spark 2.3.3, GeoTrellis 2.3.0, GeoMesa 2.2.1, JTS 1.16.0.
89
* Build `pyrasterframes` binary distribution for pip installation.
910
* Added support for rendering RasterFrame types in IPython/Jupyter.

pyrasterframes/src/main/python/docs/concepts.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Concepts
22

3-
There are a number of EO concepts that crop up in the discussion of RasterFrames features. We'll cover these briefly in the sections below. However, here are a few links providing a more extensive introduction to working with Earth observation data.
3+
There are a number of Earth-observation (EO) concepts that crop up in the discussion of RasterFrames features. We'll cover these briefly in the sections below. However, here are a few links providing a more extensive introduction to working with Earth observation data.
44

55
* [_Fundamentals of Remote Sensing_](https://www.nrcan.gc.ca/maps-tools-and-publications/satellite-imagery-and-air-photos/tutorial-fundamentals-remote-sensing/9309)
66
* [_Newcomers Earth Observation Guide_](https://business.esa.int/newcomers-earth-observation-guide)
@@ -51,7 +51,13 @@ An extent (or bounding box) is a rectangular region specifying the geospatial co
5151

5252
## Tile
5353

54-
A tile (sometimes called a "chip") is a sub-region of a scene, usually square, usually with dimensions that are some power of 2. It is usually encoded as a two-dimensional array, or a a one-dimensional array with width/height dimensions. Tiles may be singleband or multiband (multi-channel). The tile is the primary discretization unit used in RasterFrames; the bands of a scene are separated into different columns, and the band's raster is carved up into tiles for each row.
54+
A tile (sometimes called a "chip") is a rectangular subset of a @ref:[scene](concepts.md#scene). A tile can conceptually be though of as a two-dimensional array.
55+
56+
Some EO data has many bands or channels. Tiles in this context are conceptually a three-dimensional array, with the extra dimension representing the bands.
57+
58+
Tiles are often square and the dimensions are some power of two, for example 256 by 256.
59+
60+
The tile is the primary discretization unit used in RasterFrames. Each band of a scene is in a separate column. The scene's overall @ref:[extent](concepts.md#extent) is carved up into smaller extents for each tile. Each row of the DataFrame contains a two-dimensional tile per band column.
5561

5662
## Projected Extent
5763

0 commit comments

Comments
 (0)