Landscape Modeling

tutorial
ecology
grass
python

Landscape modeling with GRASS.

Author

Brendan Harmon

Published

September 6, 2026

Modified

September 12, 2026

Landscape modeling

Learn how to generate fractal terrain, derive landcover from the terrain, and draw landcover with the raster digitizer.

Landcover categories
Value Category
11 Open Water
21 Developed, Open Space
22 Developed, Low Intensity
23 Developed, Medium Intensity
24 Developed, High Intensity
31 Barren Land
41 Deciduous Forest
42 Evergreen Forest
43 Mixed Forest
52 Shrub/Scrub
71 Grassland/Herbaceuous
81 Pasture/Hay
82 Cultivated Crops
90 Woody Wetlands
95 Emergent Herbaceuous Wetlands

Setup

Start a GRASS session in a project with a Cartesian coordinate system.

# Import modules
import io
import sys
import subprocess
from pathlib import Path
from contextlib import suppress

# Find GRASS Python packages
sys.path.append(
  subprocess.check_output(
    ["grass", "--config", "python_path"],
    text=True
    ).strip()
  )

# Import GRASS packages
import grass.script as gs
import grass.jupyter as gj
from grass.tools import Tools

# Create and initialize GRASS project
try:
    gj.init("cartesian")
except ValueError:
    with suppress(ValueError):
        gs.create_project("cartesian")
        gj.init("cartesian")
tools = Tools(overwrite=True)
tools.g_proj(format="plain", flags="p").text

Set computational region

g.region

# Set region
tools.g_region(n=200, s=0, e=800, w=0, res=1)

Generate fractal terrain

r.surf.fractal

# Generate fractal terrain
tools.r_surf_fractal(output="fractal", dimension=2.25, seed=24)

# Model river valleys
tools.r_mapcalc(expression="terrain = abs(fractal) / 10")

# Visualize
m = gj.Map(width=800)
m.d_rast(map="terrain")
m.d_legend(raster="terrain", at=(5, 95, 1, 3))
m.show()
m.save("images/landscape-01.webp")

# Print elevation range
info = tools.r_info(map="terrain", flags="r", format="json")
print(f"Mininum elevation: {info["min"]:05.2f}")
print(f"Maximum elevation: {info["max"]:05.2f}")

Fractal terrain

Model rivers

r.mapcalc r.colors

# Derive river from elevation gradient
tools.r_mapcalc(expression = "landcover = if(terrain <= 2, 11, 31)")

# Derive wetlands from elevation gradient
tools.r_mapcalc(expression = "landcover = if(terrain > 2 && terrain <= 6, 95, landcover)")

# Derive woody wetlands from elevation gradient
tools.r_mapcalc(expression = "landcover = if(terrain > 6 && terrain < 10, 90, landcover)")

# Set landcover colors
tools.r_colors(map="landcover", color="nlcd")

# Visualize
m = gj.Map(width=800)
m.d_rast(map="landcover")
m.show()
m.save("images/landscape-02.webp")

River valleys

Classify landforms

r.grow r.geomorphon

# Classify landforms
tools.r_grow(input="terrain", output="elevation", radius=25)
tools.r_geomorphon(elevation="elevation", forms="landforms", search=25, skip=5, flat=1, dist=0)

# Visualize
m = gj.Map(width=800)
m.d_rast(map="landforms")
m.show()
m.save("images/landscape-03.webp")

Landforms

Model uplands

r.mapcalc

# Derive grass from elevation gradient
tools.r_mapcalc(expression = "landcover = if(terrain > 15, 71, landcover)")

# Derive shrubs from elevation gradient
tools.r_mapcalc(expression = "landcover = if(terrain > 30, 52, landcover)")

# Derive shrubs from landforms
tools.r_mapcalc(expression = "landcover = if(landcover == 71 & terrain > 25 & landforms < 7, 52, landcover)")

# Derive deciduous forest from elevation gradient
tools.r_mapcalc(expression = "landcover = if(terrain > 35, 41, landcover)")

# Derive mixed forest from landforms
tools.r_mapcalc(expression = "landcover = if(terrain > 35 && landforms == 6, 43, landcover)")

# Derive evergreen forest from landforms
tools.r_mapcalc(expression = "landcover = if(terrain > 35 && landforms < 6, 42, landcover)")

# Visualize
m = gj.Map(width=800)
m.d_rast(map="landcover")
m.show()
m.save("images/landscape-04.webp")

Uplands

Model wetlands

r.fill_dir r.mapcalc

# Find depressions
tools.r_fill_dir(input="terrain", output="fill", areas="areas", direction="direction")

# Calculate depth
tools.r_mapcalc(expression = "ponds = terrain - fill")

# Derive ponds from depressions
tools.r_mapcalc(expression = "landcover = if(terrain > 10 && terrain < 30 && ponds < -0.5, 11, landcover)")

# Derive wetlands from depressions
tools.r_mapcalc(expression = "landcover = if(terrain > 10 && terrain < 30 && ponds < -0.5  && ponds > -1.0, 95, landcover)")

# Visualize
m = gj.Map(width=800)
m.d_rast(map="landcover")
m.show()
m.save("images/landscape-05.webp")

Uplands

Define landcover categories

categories = """\
11|Open Water
12|Perennial Ice/Snow
21|Developed, Open Space
22|Developed, Low Intensity
23|Developed, Medium Intensity
24|Developed, High Intensity
31|Barren Land
41|Deciduous Forest
42|Evergreen Forest
43|Mixed Forest
51|Dwarf Scrub
52|Shrub/Scrub
71|Grassland/Herbaceuous
72|Sedge/Herbaceous
73|Lichens
74|Moss
81|Pasture/Hay
82|Cultivated Crops
90|Woody Wetlands
95|Emergent Herbaceuous Wetlands
"""

r.category

# Set landcover categories
tools.r_category(map="landcover", separator="pipe", rules=io.StringIO(categories))

# Visualize
m = gj.Map(width=800)
m.d_rast(map="landcover")
m.d_legend(raster="landcover", at=(5, 95, 1, 3), use=(11,31,41,42,43,52,71,90,95))
m.show()
m.save("images/landscape-06.webp")

Uplands

Draw landcover

g.gui.rdigit

# Generate fractal terrain
# tools.r_surf_fractal(output="fractal", dimension=2.25, seed=27)
tools.r_surf_fractal(output="fractal", dimension=2.01, seed=27)

# Model river valleys
tools.r_mapcalc(expression="terrain = abs(fractal) / 10")

# Derive river from elevation gradient
tools.r_mapcalc(expression = "landcover = if(terrain <= 2, 11, 31)")

# Derive wetlands from elevation gradient
tools.r_mapcalc(expression = "landcover = if(terrain > 2 && terrain <= 6, 95, landcover)")

# Derive woody wetlands from elevation gradient
tools.r_mapcalc(expression = "landcover = if(terrain > 6 && terrain < 10, 90, landcover)")

# Set landcover colors
tools.r_colors(map="landcover", color="nlcd")

# Visualize
m = gj.Map(width=800)
m.d_rast(map="landcover")
m.show()
m.save("images/landscape-07.webp")

Uplands

g.gui.rdigit

# Digitize
tools.g_gui_rdigit(edit="landcover", type="CELL")

# Set landcover categories
tools.r_category(map="landcover", separator="pipe", rules=io.StringIO(categories))

# Visualize
m = gj.Map(width=800)
m.d_rast(map="landcover")
m.d_legend(raster="landcover", at=(5, 95, 1, 3), use=(11,31,41,42,43,52,71,90,95))
m.show()
m.save("images/landscape-08.webp")

Uplands