Skip to main content

Hillshade (Shaded Relief)

HillshadeRasterTileLayer renders shaded relief from an RGB-encoded elevation data source. The fork extends it with multiple algorithms, smooth exaggeration, shader contours and a custom shader base class.

Hillshade relief

Hillshade relief over a vector basemap, shaded from a terrarium DEM (demo capture).

Basic usage

import com.massifmaps.layers.HillshadeRasterTileLayer
import com.massifmaps.layers.HillshadeMethod
import com.massifmaps.datasources.HTTPTileDataSource

val dem = HTTPTileDataSource(0, 12, "https://your.tiles/dem/{z}/{x}/{y}.png").apply {
setMetaDataElement("dem_encoding", Variant("terrarium")) // or "mapbox"
}

val hillshade = HillshadeRasterTileLayer(dem).apply {
setHillshadeMethod(HillshadeMethod.MULTIDIRECTIONAL)
setExaggeration(1.3f) // smooth per-frame relief factor (shader uniform)
setIlluminationDirection(315f) // light azimuth in degrees
}
mapView.layers.add(hillshade)

The illumination is also influenced by the main light source configured on Options.

With the surface API

Every setter below is a property, so the whole layer is one spec:

map.addLayer("hillshade", Spec.of("hillshade")
.set("source", Spec.of("http")
.set("url", "https://your.tiles/dem/{z}/{x}/{y}.png")
.set("maxZoom", 12)
// Attached to every tile the source loads; it is what picks the elevation decoder.
.set("metaData", Spec.object().set("dem_encoding", "terrarium")))
.set("hillshadeMethod", "HILLSHADE_METHOD_MULTIDIRECTIONAL")
.set("exaggeration", 1.3)
.set("contrast", 0.6));

// Animatable afterwards — a shader uniform, no re-decode.
map.layer("hillshade").set("exaggeration", 1.8);

Every property and its enum values: hillshade in the layer reference.

Hillshade methods

setHillshadeMethod(...) selects the algorithm:

MethodDescription
STANDARDMapLibre's legacy hillshade algorithm.
COMBINEDCombined algorithm based on GDAL.
IGORIgor's soft hillshade (GDAL).
MULTIDIRECTIONALMultiple light sources for richer relief.
BASICBasic algorithm based on GDAL.

Appearance controls

SetterPurpose
setExaggeration(float)Per-frame relief factor (no re-decode). Default 1.0.
setIlluminationDirection(float)Light azimuth (degrees).
setHeightScale(float) / setExagerateHeightScaleEnabled(bool)Vertical scale of the elevation.
setContrast(float)Shading contrast.
setShadowColor / setAccentColor / setHighlightColorTint the shadow / accent / highlight bands.
setIlluminationMapRotationEnabled(bool)Rotate the light with the map.

Shader contours

The hillshade layer can also draw contour lines in the fragment shader from the same elevation — see On-the-fly Contours:

hillshade.setContourEnabled(true)
hillshade.setContourInterval(100f)
hillshade.setContourColor(Color(0xFF7A4B28.toInt()))
hillshade.setContourWidth(1.0f)

Custom lighting shader

For full control, enable elevation encoding and provide your own GLSL lighting shader (it can read getElevation() and getMapZoom()):

hillshade.setElevationEncodingEnabled(true)
hillshade.setNormalMapLightingShader(myGlslSource)

HillshadeRasterTileLayer is a specialization of CustomRasterTileLayer — the DEM-specific case of the general custom-shader raster layer.

Sharing the DEM with 3D terrain

The same elevation source can drive both hillshade and 3D terrain. Wrap it in a MemoryCacheTileDataSource so both features share tiles:

val dem = MemoryCacheTileDataSource(httpDem)
mapView.layers.add(HillshadeRasterTileLayer(dem))
mapView.options.terrainOptions = TerrainOptions(dem).apply { isEnabled = true }

You can also fold hillshade into a single style with the Composite Vector Tile Layer.