Landscape Mosaic#

Open In Colab

This notebook demonstrates the Landscape Mosaic tool which analyses the compositional diversity of a three-class land cover map using a moving window approach. Each pixel is classified based on the proportional composition of the three land cover classes within the window, resulting in up to 103 compositional classes subsequently aggregated to 19 classes.

We will run the analysis twice with two different colour schemes to highlight different aspects of the landscape:

  • BGR (Background): emphasises the dominant land cover class in each window

  • DIV (Diversity): emphasises landscape compositional diversity

Input data: Three-class land cover map derived from Corine Land Cover 2018 at 100m resolution, Corsica, France:

Pixel Value

Class

CLC Correspondence

1

Agriculture

Agricultural surfaces

2

Natural

Natural vegetation, forests, wetlands

3

Developed

Artificial surfaces

0

NoData

Water bodies, outside extent

Analysis parameters: window size 27x27 pixels (2.7km x 2.7km, ~729 ha at 100m resolution)

1. Import Libraries and Define Paths#

[ ]:
# Install pyguidos if running in Google Colab
try:
    import pyguidos as pg
except ImportError:
    !pip install -q pyguidos
    import pyguidos as pg
from pyguidos import utils
print(f"pyGuidos version: {pg.__version__}")

# --- Data paths ---
lm_tiff  = pg.DATA_DIR / "CLC2018_corsica_LandMos.tif"
[ ]:
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.colors import ListedColormap, BoundaryNorm
from PIL import Image as PILImage
import rasterio
[ ]:
# --- Load the Output directory ---
CONFIG_PATH = pg.PROJECT_ROOT / ".notebook_config"
if CONFIG_PATH.exists():
    try:
        OUT_DIR = Path(CONFIG_PATH.read_text(encoding="utf-8").strip())
        print(f"Workspace synced: {OUT_DIR}")
    except Exception as e:
        print(f"Error reading config, using default. {e}")
        OUT_DIR = pg.PROJECT_ROOT / "output"
else:
    # Fallback if the user skipped Notebook 1
    OUT_DIR = pg.PROJECT_ROOT / "output"
    print(f"Config not found. Using default: {OUT_DIR}")

OUT_DIR.mkdir(parents=True, exist_ok=True)

2. Helper Function — Render GTB Colormap#

[ ]:
def gtb_colormap(tiff_path):
    """
    Reads the embedded GTB colormap from a pyGuidos output GeoTIFF
    and returns a matplotlib ListedColormap and Normalize.
    """
    with rasterio.open(tiff_path) as src:
        data = src.read(1)
        cmap_dict = src.colormap(1)

    colors = np.zeros((256, 4), dtype=np.float32)
    for val, rgba in cmap_dict.items():
        if 0 <= val < 256:
            colors[val] = [c / 255.0 for c in rgba]

    cmap = ListedColormap(colors)
    norm = plt.Normalize(vmin=0, vmax=255)

    return data, cmap, norm

3. Inspect Input Map#

[ ]:
# Metadata
info = utils.get_raster_info(lm_tiff)
print(f"Size      : {info['rows']} rows x {info['cols']} cols")
print(f"Dtype     : {info['dtype']}")
print(f"Resolution: {info['resX']} x {info['resY']} m")
print(f"EPSG      : {info['epsg']}")

# Pixel frequencies
with rasterio.open(lm_tiff) as src:
    lm_data = src.read(1)

lm_freq = utils.get_pxl_freq(lm_data)
tot = info['rows'] * info['cols']

print("\nPixel value distribution:")
labels = {0: 'NoData', 1: 'Agriculture', 2: 'Natural', 3: 'Developed'}
for val, name in labels.items():
    n = lm_freq.get(val, 0)
    print(f"  Value {val} — {name:<15}: {n:>10} px  ({n/tot*100:6.2f}%)")
[ ]:
# Visualise input map
lm_colors_list = ['white', 'gold', 'darkgreen', 'firebrick']
lm_cmap_plt    = ListedColormap(lm_colors_list)
lm_norm_plt    = BoundaryNorm([0, 1, 2, 3, 4], lm_cmap_plt.N)

fig, ax = plt.subplots(figsize=(7, 9))
ax.imshow(lm_data, cmap=lm_cmap_plt, norm=lm_norm_plt, interpolation='none')
ax.set_title('Land Mosaic Input Map — Corsica\nCLC 2018, 100m resolution', fontsize=13, pad=15)
ax.axis('off')

legend_patches = [
    mpatches.Patch(facecolor='white', edgecolor='black', label='NoData (0)'),
    mpatches.Patch(color='gold',      label='Agriculture (1)'),
    mpatches.Patch(color='darkgreen', label='Natural (2)'),
    mpatches.Patch(color='firebrick', label='Developed (3)'),
]
ax.legend(handles=legend_patches, loc='upper left', fontsize=11, framealpha=0.9)
plt.tight_layout()
plt.show()

4. Landscape Mosaic — BGR Color Scheme#

The BGR (Background) color scheme emphasises the dominant land cover class in each moving window. Areas where one class strongly dominates are shown in pure blue (Agriculture), green (Natural) or red (Developed). Mixed areas appear in intermediate transition colors.

This color scheme is useful for identifying the spatial distribution of dominant land cover types and their transition zones.

[ ]:
print("Running Landscape Mosaic (BGR, window=27)...")
lm_bgr_result = pg.landmos(
    in_tiff=lm_tiff,
    window_size=27,
    outdir=OUT_DIR,
    out_colors='bgr',
    statists=True,
    stat_files=True,
    verb=True
)
[ ]:
print("### 📂 Output Paths")
for key, path in lm_bgr_result['output paths'].items():
    print(f"{key:<15}: {path}")

print("### 📊 Input Statistics")
for key, path in lm_bgr_result['input stats'].items():
    print(f"{key:<15}: {path}")
[ ]:
# --- BGR Statistics ---
print("Input pixel counts:")
for k, v in lm_bgr_result['input stats'].items():
    print(f"  {k:<20}: {v:>10}")

# 19-class pixel counts
print("\n19-class pixel counts:")
foregr = lm_bgr_result['input stats']['foreground pxl']
freq_19 = lm_bgr_result['output stats']['pxl numb 19cl']

class_19_names = {
    1:  'A     — Agriculture dominant',
    2:  'D     — Developed dominant',
    3:  'N     — Natural dominant',
    4:  'Ad    — Agriculture with Developed',
    5:  'An    — Agriculture with Natural',
    6:  'Dn    — Developed with Natural',
    7:  'Da    — Developed with Agriculture',
    8:  'Na    — Natural with Agriculture',
    9:  'Nd    — Natural with Developed',
    10: 'Adn   — Agriculture dominant mixed',
    11: 'Dan   — Developed dominant mixed',
    12: 'Nad   — Natural dominant mixed',
    13: 'ad    — Agriculture-Developed transition',
    14: 'an    — Agriculture-Natural transition',
    15: 'dn    — Developed-Natural transition',
    16: 'adn   — Mixed transition',
    17: 'NN    — Pure Natural (100%)',
    18: 'AA    — Pure Agriculture (100%)',
    19: 'DD    — Pure Developed (100%)',
}

print(f"  {'Class':<45} {'Pixels':>10} {'% FG':>8}")
print("  " + "-" * 68)
for cls, name in class_19_names.items():
    n = freq_19.get(cls, 0)
    pct = n / foregr * 100 if foregr > 0 else 0
    print(f"  {name:<45} {n:>10} {pct:>8.2f}%")
[ ]:
# --- Visualise BGR output with GTB colormap + ternary heatmap ---
bgr_tiff_103 = lm_bgr_result['output paths']['path tif 103cl']
bgr_png     = lm_bgr_result['output paths']['path png']

data_bgr, cmap_bgr, norm_bgr = gtb_colormap(bgr_tiff_103)

fig, axes = plt.subplots(1, 2, figsize=(16, 9),
                          gridspec_kw={'width_ratios': [1, 1.2]})

# Left — map
axes[0].imshow(data_bgr, cmap=cmap_bgr, norm=norm_bgr, interpolation='none')
axes[0].set_title('Landscape Mosaic (BGR) — Corsica\nCLC 2018, 100m\nWindow: 27x27 pixels (2.7km x 2.7km)',
                  fontsize=12, pad=15)
axes[0].axis('off')

# Right — ternary heatmap
heatmap_img = PILImage.open(bgr_png)
axes[1].imshow(heatmap_img)
axes[1].set_title('Ternary Diagram Heatmap (BGR)', fontsize=12, pad=15)
axes[1].axis('off')

plt.tight_layout()
plt.show()

5. Landscape Mosaic — DIV Color Scheme#

The DIV (Diversity) color scheme emphasises landscape compositional diversity. Areas dominated by a single class appear in cool blue tones, while areas with high compositional diversity (mixture of all three classes) appear in warm red/orange tones.

This color scheme is useful for identifying biodiversity hotspots and ecotones — transition zones between different land cover types — which are often ecologically significant areas.

[ ]:
print("Running Landscape Mosaic (DIV, window=27)...")
lm_div_result = pg.landmos(
    in_tiff=lm_tiff,
    window_size=27,
    outdir=OUT_DIR,
    out_colors='div',
    statists=True,
    stat_files=True,
    verb=True
)
[ ]:
# --- Visualise DIV output with GTB colormap + ternary heatmap ---
bgr_tiff_103 = lm_div_result['output paths']['path tif 103cl']
div_png     = lm_div_result['output paths']['path png']

data_div, cmap_div, norm_div = gtb_colormap(bgr_tiff_103)

fig, axes = plt.subplots(1, 2, figsize=(16, 9),
                          gridspec_kw={'width_ratios': [1, 1.2]})

# Left — map
axes[0].imshow(data_div, cmap=cmap_div, norm=norm_div, interpolation='none')
axes[0].set_title('Landscape Mosaic (DIV) — Corsica\nCLC 2018, 100m\nWindow: 27x27 pixels (2.7km x 2.7km)',
                  fontsize=12, pad=15)
axes[0].axis('off')

# Right — ternary heatmap
heatmap_img = PILImage.open(div_png)
axes[1].imshow(heatmap_img)
axes[1].set_title('Ternary Diagram Heatmap (DIV)', fontsize=12, pad=15)
axes[1].axis('off')

plt.tight_layout()
plt.show()

6. BGR vs DIV — Side by Side Comparison#

Comparing both color schemes reveals complementary spatial patterns:

  • BGR shows where each land cover type dominates

  • DIV shows where the landscape is most compositionally diverse

[ ]:
fig, axes = plt.subplots(1, 2, figsize=(16, 10))

# BGR
axes[0].imshow(data_bgr, cmap=cmap_bgr, norm=norm_bgr, interpolation='none')
axes[0].set_title('BGR — Dominant Land Cover\n(blue=Agriculture, green=Natural, red=Developed)',
                  fontsize=12, pad=15)
axes[0].axis('off')

# DIV
axes[1].imshow(data_div, cmap=cmap_div, norm=norm_div, interpolation='none')
axes[1].set_title('DIV — Landscape Diversity\n(blue=uniform, red/orange=high diversity)',
                  fontsize=12, pad=15)
axes[1].axis('off')

fig.suptitle('Landscape Mosaic — Corsica, CLC 2018 (100m), Window 27x27',
             fontsize=14, y=1.01)
plt.tight_layout()
plt.show()

7. Summary#

In this notebook we have applied the Landscape Mosaic tool to the Corsica three-class land cover map:

  • The BGR color scheme reveals that Natural vegetation strongly dominates the interior mountainous areas of Corsica, while Agriculture and Developed classes are concentrated along the coastal plains

  • The DIV color scheme highlights the ecotone zones between land cover types, particularly along the boundaries between the natural interior and the agricultural and urban coastal areas

  • The ternary heatmap provides a quantitative summary of the overall landscape composition, showing the proportion of pixels in each of the 103 compositional classes

In the next notebook we will perform fragmentation change analysis comparing Corsica between two time periods.

[ ]: