Lyngen Alps — Avalanche Risk
Location: Lyngen Alps, Tromsø county, northern Norway (69.35°N–69.75°N, 19.8°E–21.0°E)
This example demonstrates pixel-wise avalanche risk inference over real Norwegian terrain using a free WCS endpoint. No credentials are required.
What it demonstrates
- Fetching a real 10 m Digital Terrain Model from Kartverket's WCS
- Deriving slope angle and aspect analytically from the DEM
- Using
ConstantSourcefor spatially-uniform weather inputs - Encoding domain knowledge (terrain + weather) in a 2-level BN
- Exploring different weather scenarios by changing two scalar constants
Bayesian network structure
slope_angle ──┐
├──► terrain_factor ──┐
sun_exposure ──┘ ├──► avalanche_risk
recent_snow ──┐ │
├──► weather_factor ──┘
temperature ──┘
Four root nodes (evidence inputs), two intermediate nodes, one query node
(avalanche_risk) with states {low, medium, high}.
Data sources
| Node | Source | Notes |
|---|---|---|
slope_angle |
WCSSource (Kartverket DTM) → numpy.gradient |
degrees (0–90°) |
sun_exposure |
WCSSource (Kartverket DTM) → numpy.gradient |
aspect quadrant (0=N, 1=E, 2=W, 3=S) |
recent_snow |
ConstantSource |
cm; edit RECENT_SNOW_CM to change scenario |
temperature |
ConstantSource |
°C; edit AIR_TEMP_C to change scenario |
Annotated walkthrough
1. Define the study area and grid
WEST, SOUTH, EAST, NORTH = 19.8, 69.35, 21.0, 69.75
CRS = "EPSG:4326"
RESOLUTION = 0.005 # ~200 m at 70°N → 80 rows × 240 cols
H = round((NORTH - SOUTH) / RESOLUTION) # 80
W = round((EAST - WEST) / RESOLUTION) # 240
transform = Affine(RESOLUTION, 0, WEST, 0, -RESOLUTION, NORTH)
ref_grid = GridSpec(crs=CRS, transform=transform, shape=(H, W))
2. Fetch the DTM
dem = bn.fetch_raw(geobn.WCSSource(
url="https://hoydedata.no/arcgis/services/las_dtm_somlos/ImageServer/WCSServer",
layer="las_dtm",
version="1.0.0",
valid_range=(-500.0, 9000.0),
cache_dir=CACHE_DIR,
)) # (80, 240) float32, NaN at sea
The terrain is cached after the first run. On subsequent runs it loads from
examples/lyngen_alps/cache/ without making a network request.
3. Derive slope and aspect
slope_deg, north_facing = compute_slope_aspect(dem)
# slope_deg: float32 (H, W), range 0–90°
# north_facing: float32 (H, W), 1.0 = N-facing, 0.0 = S-facing
The pixel-metre conversion accounts for the geographic CRS:
m_per_deg_lat = 111_320.0
m_per_deg_lon = 111_320.0 * np.cos(np.radians(lat_mid))
dz_drow, dz_dcol = np.gradient(dem_filled, pixel_lat_m, pixel_lon_m)
4. Load the BN and wire inputs
bn = geobn.load("avalanche_risk.bif")
bn.set_grid(CRS, RESOLUTION, (WEST, SOUTH, EAST, NORTH))
bn.set_input("slope_angle", geobn.ArraySource(slope_deg))
bn.set_input("sun_exposure", geobn.ArraySource(sun_exposure))
bn.set_input("recent_snow", geobn.ConstantSource(RECENT_SNOW_CM))
bn.set_input("temperature", geobn.ConstantSource(AIR_TEMP_C))
5. Configure discretization
bn.set_discretization("slope_angle", [0, 5, 25, 40, 90])
bn.set_discretization("sun_exposure", [-0.5, 0.5, 1.5, 2.5, 3.5])
bn.set_discretization("recent_snow", [0, 10, 25, 150])
bn.set_discretization("temperature", [-40, -8, -2, 15])
6. Run inference and export
result = bn.infer(query=["avalanche_risk"])
result.show_map(OUT_DIR, extra_layers={"Slope angle (°)": slope_deg})
result.to_geotiff(OUT_DIR)
Key outputs
output/map.html— interactive Leaflet map with risk probability overlay, entropy overlay, slope angle overlay, and layer switcheroutput/avalanche_risk.tif— 4-band GeoTIFF: Band 1 P(low), Band 2 P(medium), Band 3 P(high), Band 4 entropy
How to run
Exploring weather scenarios
Edit the two constants at the top of run_example.py:
Try RECENT_SNOW_CM = 5.0 (light snow) and AIR_TEMP_C = 0.5 (warming/wet) to
see how the risk distribution changes across the terrain.