Equations & Formulas
The exact math behind every terrain-derivative visualization mode
Every formula on this page is transcribed directly from its lib/*-protocol.ts source, not re-derived — see that file for the full derivation/attribution comments. For how these values get from a formula to a rendered layer, see Terrain Analysis Rendering Pipeline, LRM, and Lighting Effects. Sun/light-position astronomy (declination, hour angle, azimuth/elevation) is covered separately on Sun Position, since it drives Phong's light direction and the shadow calculator rather than being a terrain derivative itself.
Shared inputs
Elevation decoding (lib/elevation-encoding.ts) — every mode starts by decoding an upstream raster-dem tile:
The Horn 3×3 gradient (hornGradient(), ported from GDAL's GDALSlopeHornAlg), the shared building block for Slope, Aspect, TRI*, Curvature, and (per-sub-cell) Blobness. The window is named row-major — GDAL's own afWin[0..8] convention, kept for line-by-line parity with the ported code — laid out on the tile grid as ( = column = east, = row = south):
The Horn 3×3 gradient window — ported from GDAL's GDALSlopeHornAlg
| west (col −1) | center col | east (col +1) | |
|---|---|---|---|
| north (row −1) | |||
| center row | (center) | ||
| south (row +1) |
With ground resolution at the tile's center latitude:
*TRI/TPI/Roughness don't use the gradient directly — they're plain 3×3 aggregations of elevation itself, see below.Terrain analysis
Slope
Re-encoded via Mapbox Terrain-RGB packing (base −10000, 0.1° step). lib/slope-protocol.ts.
Aspect
Compass bearing of the gradient direction, converted from math convention (CCW from east) to compass convention (CW from north):
(0 if , i.e. flat ground.) lib/aspect-protocol.ts.
TRI (Terrain Ruggedness Index)
Root-mean-square elevation difference to the 8 immediate neighbors (Riley et al. 2006), in source elevation units (meters):
lib/tri-protocol.ts.
TPI (Topographic Position Index)
Center elevation minus the mean of its 8 neighbors:
Positive → above the local neighborhood (ridge/peak); negative → below it (valley/pit). lib/tpi-protocol.ts.
Roughness
lib/roughness-protocol.ts.
Curvature
Four sub-modes share one Zevenbergen & Thorne (1987) second-order fit over the 3×3 window ( = first partials; = second partials; = ground spacing):
-
Combined — discrete Laplacian, ×100 (no single canonical GDAL algorithm to port from, unlike slope/aspect/TRI):
-
Profile (curvature along steepest descent — flow acceleration) / Plan (curvature across contours — flow convergence/divergence, ), both ×100, undefined () on flat ground:
-
Det-Hessian — , a blob/saddle detector (positive at bowl/dome extrema, negative at saddles, ~0 on a uniform slope or straight ridge), ×10,000:
-
Casorati / Shape Index — both derived from the principal curvatures (eigenvalues of the Hessian ), the small-slope approximation:
( dome/peak, ridge, saddle, valley, pit/bowl.)
All modes ×CURVATURE_ENCODE_SCALE = 1000 at the wire-encoding step only (undone on read), to spread the small near-zero-heavy range across more of Terrarium's discrete levels. lib/curvature-protocol.ts.
Blobness / Eigen-Ratio / Orientation
A Förstner/Harris-style structure tensor , where each entry is the 3×3-box average of , , — the Horn gradient computed at each of the window's 9 sub-cells (needs a 5×5 halo, since each sub-cell's own gradient needs its own 3×3 neighborhood):
-
Blobness — the corrects for the Horn kernel's gradient inflation ( is degree-4 in the gradient, so ) and rescales to roughly match TRI/Roughness's visual range. High where gradient direction varies in every direction (peaks/pits/saddles/knolls); near zero on a uniform slope or straight ridge, however steep.
-
Eigen-Ratio — shape only, independent of magnitude: 0 on a perfectly coherent edge (slope/ridge/valley), 100 on a perfectly isotropic blob (peak/pit/saddle). The inflation cancels in this ratio, so no extra scale correction is needed.
-
Orientation — dominant eigenvector's axis, folded to – (a line has no inherent direction, only an axis):
lib/blobness-protocol.ts.
Relief visualization
Horizon angle (shared core)
lib/horizon-angle.ts — for each of 8 compass directions, march outward pixel-by-pixel (up to a user-set search radius) and find the steepest elevation angle to any point along the ray:
SVF and Openness apply different aggregations to this same set of 8 angles (Zakšek, Oštir & Kokalj, 2011).
Sky-View Factor (SVF)
Each angle is clamped to first (a ray dipping downhill still leaves the entire sky visible in that direction — SVF can't exceed "fully open"), then:
lib/svf-protocol.ts.
Openness (Positive / Negative)
Mean angular distance from zenith to the horizon, not clamped to (so it can read above/below what SVF calls "fully open" — e.g. standing on a summit looking outward gives a negative angle in every direction). Negative Openness is Positive Openness computed on the terrain's mirror image (elevation differences × ), turning pits into the "peaks" the same formula highlights.
lib/openness-protocol.ts.
Local Dominance
Places a virtual observer of eye height at each pixel and averages the downward-looking angle to the surrounding terrain across several compass directions and distances (Hesse, 2016):
averaged over sampled directions × distances. Positive/high on local highs that look down on their surroundings (mounds, ridges, tells); low/negative in enclosed depressions. Distances beyond a near-field threshold are sampled from pyramid ancestor tiles (one ring per octave) rather than at native resolution — the same trick LRM uses, valid here because this quantity is a mean (a coarse per-octave sample is a legitimate low-pass proxy), unlike SVF/Openness's max. lib/local-dominance-protocol.ts.
LRM (Local Relief Model)
Not a neighborhood formula — see the dedicated LRM page for the full pyramid-ancestor mechanism:
Lighting effects
See Lighting Effects for the full pipeline (surface normals, live-GL fast path, staleness guards); formulas only, here.
Surface normal (shared by Matcap, Phong, Hard Shadows' input)
Standard heightfield-to-normal formula from the Horn gradient, object-space (not tangent-space):
Encoded as . lib/normals-protocol.ts.
Matcap
Rotated normal used directly as a texture UV (orthographic simplification):
lib/matcap-protocol.ts.
Phong (Blinn-Phong)
Light vector from compass azimuth/altitude:
(signs empirically pinned against MapLibre's native hillshade shader — see Lighting Effects). total is then split into a multiply-darken regime () and a screen-brighten regime () for compositing — see that page for the exact alpha encoding. lib/phong-protocol.ts.
Hard Shadows
Single-ray horizon march toward the sun's actual azimuth (not snapped to a compass tick), compared against the sun's altitude:
lib/shadow-protocol.ts.