Skip to content

Commit 1c87c11

Browse files
authored
Merge pull request #645 from jbouffard/improvement/hillshade
The hillshade Function Will Now Use the ZFactorCalculator
2 parents 13db9d2 + 670794c commit 1c87c11

File tree

5 files changed

+84
-53
lines changed

5 files changed

+84
-53
lines changed

geopyspark-backend/geotrellis/src/main/scala/geopyspark/geotrellis/SpatialTiledRasterLayer.scala

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import geotrellis.raster.histogram._
1717
import geotrellis.raster.io.geotiff._
1818
import geotrellis.raster.io.geotiff.compression._
1919
import geotrellis.raster.mapalgebra.focal.{Square, Slope}
20+
import geotrellis.raster.mapalgebra.focal.hillshade._
2021
import geotrellis.raster.rasterize._
2122
import geotrellis.raster.render._
2223
import geotrellis.raster.resample.{ResampleMethod, PointResampleMethod, Resample}
@@ -227,6 +228,35 @@ class SpatialTiledRasterLayer(
227228
)
228229
}
229230

231+
def hillshade(
232+
azimuth: Double,
233+
altitude: Double,
234+
zFactorCalculator: ZFactorCalculator,
235+
band: Int
236+
): SpatialTiledRasterLayer = {
237+
val mt = rdd.metadata.mapTransform
238+
val cellSize = rdd.metadata.cellSize
239+
val neighborhood = Square(1)
240+
val gridBounds = rdd.metadata.gridBounds
241+
val partitioner = rdd.partitioner
242+
243+
SpatialTiledRasterLayer(
244+
zoomLevel,
245+
rdd.withContext { rdd =>
246+
rdd.bufferTiles(neighborhood.extent, gridBounds, partitioner).mapPartitions[(SpatialKey, MultibandTile)](
247+
{ iter =>
248+
iter.map { case (key, BufferedTile(tile, bounds)) =>
249+
val zfactor = zFactorCalculator.deriveZFactor(mt.keyToExtent(key))
250+
val hillshadeTile = Hillshade(tile.bands(band), neighborhood, Some(bounds), cellSize, azimuth, altitude, zfactor)
251+
252+
key -> MultibandTile(hillshadeTile)
253+
}
254+
}, preservesPartitioning = true
255+
)
256+
}.mapContext(_.copy(cellType = ShortConstantNoDataCellType))
257+
)
258+
}
259+
230260
def mask(geometries: Seq[MultiPolygon]): TiledRasterLayer[SpatialKey] =
231261
SpatialTiledRasterLayer(zoomLevel, Mask(rdd, geometries, Mask.Options.DEFAULT))
232262

@@ -324,25 +354,6 @@ class SpatialTiledRasterLayer(
324354
SpatialTiledRasterLayer(None, multibandRDD)
325355
}
326356

327-
def hillshade(
328-
sc: SparkContext,
329-
azimuth: Double,
330-
altitude: Double,
331-
zFactor: Double,
332-
band: Int
333-
): TiledRasterLayer[SpatialKey] = {
334-
val tileLayer = TileLayerRDD(rdd.mapValues(_.band(band)), rdd.metadata)
335-
336-
implicit val _sc = sc
337-
338-
val result = tileLayer.hillshade(azimuth, altitude, zFactor)
339-
340-
val multibandRDD: MultibandTileLayerRDD[SpatialKey] =
341-
MultibandTileLayerRDD(result.mapValues{ tile => MultibandTile(tile) }, result.metadata)
342-
343-
SpatialTiledRasterLayer(None, multibandRDD)
344-
}
345-
346357
def reclassify(reclassifiedRDD: RDD[(SpatialKey, MultibandTile)]): TiledRasterLayer[SpatialKey] =
347358
SpatialTiledRasterLayer(zoomLevel, MultibandTileLayerRDD(reclassifiedRDD, rdd.metadata))
348359

geopyspark-backend/geotrellis/src/main/scala/geopyspark/geotrellis/TemporalTiledRasterLayer.scala

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import geotrellis.raster.histogram._
1515
import geotrellis.raster.io.geotiff._
1616
import geotrellis.raster.io.geotiff.compression._
1717
import geotrellis.raster.mapalgebra.focal.{Square, Slope}
18+
import geotrellis.raster.mapalgebra.focal.hillshade._
1819
import geotrellis.raster.rasterize._
1920
import geotrellis.raster.render._
2021
import geotrellis.raster.resample.{ResampleMethod, PointResampleMethod, Resample}
@@ -351,6 +352,35 @@ class TemporalTiledRasterLayer(
351352
)
352353
}
353354

355+
def hillshade(
356+
azimuth: Double,
357+
altitude: Double,
358+
zFactorCalculator: ZFactorCalculator,
359+
band: Int
360+
): TemporalTiledRasterLayer = {
361+
val mt = rdd.metadata.mapTransform
362+
val cellSize = rdd.metadata.cellSize
363+
val neighborhood = Square(1)
364+
val gridBounds = rdd.metadata.gridBounds
365+
val partitioner = rdd.partitioner
366+
367+
TemporalTiledRasterLayer(
368+
zoomLevel,
369+
rdd.withContext { rdd =>
370+
rdd.bufferTiles(neighborhood.extent, gridBounds, partitioner).mapPartitions[(SpaceTimeKey, MultibandTile)](
371+
{ iter =>
372+
iter.map { case (key, BufferedTile(tile, bounds)) =>
373+
val zfactor = zFactorCalculator.deriveZFactor(mt.keyToExtent(key))
374+
val hillshadeTile = Hillshade(tile.bands(band), neighborhood, Some(bounds), cellSize, azimuth, altitude, zfactor)
375+
376+
key -> MultibandTile(hillshadeTile)
377+
}
378+
}, preservesPartitioning = true
379+
)
380+
}.mapContext(_.copy(cellType = ShortConstantNoDataCellType))
381+
)
382+
}
383+
354384
def costDistance(
355385
sc: SparkContext,
356386
geometries: Seq[Geometry],
@@ -375,25 +405,6 @@ class TemporalTiledRasterLayer(
375405
TemporalTiledRasterLayer(None, multibandRDD)
376406
}
377407

378-
def hillshade(
379-
sc: SparkContext,
380-
azimuth: Double,
381-
altitude: Double,
382-
zFactor: Double,
383-
band: Int
384-
): TiledRasterLayer[SpaceTimeKey] = {
385-
val tileLayer = TileLayerRDD(rdd.mapValues(_.band(band)), rdd.metadata)
386-
387-
implicit val _sc = sc
388-
389-
val result = tileLayer.hillshade(azimuth, altitude, zFactor)
390-
391-
val multibandRDD: MultibandTileLayerRDD[SpaceTimeKey] =
392-
MultibandTileLayerRDD(result.mapValues(MultibandTile(_)), result.metadata)
393-
394-
TemporalTiledRasterLayer(None, multibandRDD)
395-
}
396-
397408
def reclassify(reclassifiedRDD: RDD[(SpaceTimeKey, MultibandTile)]): TiledRasterLayer[SpaceTimeKey] =
398409
TemporalTiledRasterLayer(zoomLevel, MultibandTileLayerRDD(reclassifiedRDD, rdd.metadata))
399410

geopyspark-backend/geotrellis/src/main/scala/geopyspark/geotrellis/TiledRasterLayer.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,6 @@ abstract class TiledRasterLayer[K: SpatialComponent: JsonFormat: ClassTag: Bound
198198
maxDistance: Double
199199
): TiledRasterLayer[K]
200200

201-
def hillshade(sc: SparkContext,
202-
azimuth: Double,
203-
altitude: Double,
204-
zFactor: Double,
205-
band: Int
206-
): TiledRasterLayer[K]
207-
208201
def localMax(i: Int): TiledRasterLayer[K] =
209202
withRDD(rdd.mapValues { x => MultibandTile(x.bands.map(_.localMax(i))) })
210203

@@ -402,6 +395,13 @@ abstract class TiledRasterLayer[K: SpatialComponent: JsonFormat: ClassTag: Bound
402395
}
403396
}
404397

398+
def hillshade(
399+
azimuth: Double,
400+
altitude: Double,
401+
zFactorCalculator: ZFactorCalculator,
402+
band: Int
403+
): TiledRasterLayer[K]
404+
405405
def aggregateByCell(operation: String): TiledRasterLayer[K] = {
406406
val bands: RDD[(K, Array[ArrayBuffer[Tile]])] =
407407
rdd.combineByKey(

geopyspark/geotrellis/hillshade.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
__all__ = ['hillshade']
55

66

7-
def hillshade(tiled_raster_layer, band=0, azimuth=315.0, altitude=45.0, z_factor=1.0):
7+
def hillshade(tiled_raster_layer, zfactor_calculator, band=0, azimuth=315.0, altitude=45.0):
88
"""Computes Hillshade (shaded relief) from a raster.
99
1010
The resulting raster will be a shaded relief map (a hill shading) based
11-
on the sun altitude, azimuth, and the z factor. The z factor is a
11+
on the sun altitude, azimuth, and the ``zfactor``. The ``zfactor`` is a
1212
conversion factor from map units to elevation units.
1313
14+
The ``hillshade``` operation will be carried out in a ``SQUARE`` neighborhood with with an
15+
``extent`` of 1. The ``zfactor`` will be derived from the ``zfactor_calculator``
16+
for each ``Tile`` in the Layer. The resulting Layer will have a ``cell_type``
17+
of ``INT16`` regardless of the input Layer's ``cell_type``; as well as
18+
have a single band, that represents the calculated ``hillshade``.
19+
1420
Returns a raster of ShortConstantNoDataCellType.
1521
1622
For descriptions of parameters, please see Esri Desktop's
@@ -19,17 +25,19 @@ def hillshade(tiled_raster_layer, band=0, azimuth=315.0, altitude=45.0, z_factor
1925
Args:
2026
tiled_raster_layer (:class:`~geopyspark.geotrellis.layer.TiledRasterLayer`): The base layer
2127
that contains the rasters used to compute the hillshade.
28+
zfactor_calculator (py4j.JavaObject): A ``JavaObject`` that represents the
29+
Scala ``ZFactorCalculator`` class. This can be created using either the
30+
:meth:`~geopyspark.geotrellis.zfactor_lat_lng_calculator` or the
31+
:meth:`~geopyspark.geotrellis.zfactor_calculator` methods.
2232
band (int, optional): The band of the raster to base the hillshade calculation on. Default is 0.
2333
azimuth (float, optional): The azimuth angle of the source of light. Default value is 315.0.
2434
altitude (float, optional): The angle of the altitude of the light above the horizon. Default is
2535
45.0.
26-
z_factor (float, optional): How many x and y units in a single z unit. Default value is 1.0.
2736
2837
Returns:
2938
:class:`~geopyspark.geotrellis.layer.TiledRasterLayer`
3039
"""
3140

32-
srdd = tiled_raster_layer.srdd.hillshade(tiled_raster_layer.pysc._jsc.sc(), azimuth,
33-
altitude, z_factor, band)
41+
srdd = tiled_raster_layer.srdd.hillshade(azimuth, altitude, zfactor_calculator, band)
3442

3543
return TiledRasterLayer(tiled_raster_layer.layer_type, srdd)

geopyspark/tests/geotrellis/tiled_layer_tests/hillshade_test.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
import pytest
66

77
from shapely.geometry import Point
8-
from geopyspark.geotrellis import SpatialKey, Tile
8+
from geopyspark.geotrellis import SpatialKey, Tile, zfactor_lat_lng_calculator
99
from geopyspark.tests.base_test_class import BaseTestClass
1010
from geopyspark.geotrellis import hillshade
1111
from geopyspark.geotrellis.layer import TiledRasterLayer
12-
from geopyspark.geotrellis.constants import LayerType
12+
from geopyspark.geotrellis.constants import LayerType, Unit
1313

1414

1515
class HillshadeTest(BaseTestClass):
@@ -45,7 +45,8 @@ def tearDown(self):
4545
BaseTestClass.pysc._gateway.close()
4646

4747
def test_hillshade(self):
48-
result = hillshade(self.raster_rdd, band=0, azimuth=99.0, altitude=33.0, z_factor=0.0)
48+
calc = zfactor_lat_lng_calculator(Unit.METERS)
49+
result = hillshade(self.raster_rdd, calc, band=0, azimuth=99.0, altitude=33.0)
4950

5051
data = result.to_numpy_rdd().first()[1].cells[0][0][0]
5152
self.assertEqual(data, 63)

0 commit comments

Comments
 (0)