import cartopy.crs as ccrs
import folium
import holoviews as hv
import hvplot.xarray # noqa
import shapefile
import xarray as xr
from shapely.geometry import box, mapping
Observing Vegetation Change from Space
12.1 Overview
In this notebook we will examine the Copernicus Global Land Service vegetation indices;
- fraction of absorbed photosynthetically activae radation (fAPAR)
- fraction of vegetation cover (fCOVER)
- leaf area index (LAI)
TO DO: add short descriptions of the data.
We will show here how we can use these products to track vegetation change throughout the year (phenology), whilst also spotting anomalous vegetation/land cover changes.
12.2 Imports
12.3 Study Area
For this study we will have a look at vegetation of the Corongosa National Park of Mozambiqure. The park has had a turbulent history where the park was the scene of military incursions and civilian flight in the previous decade and slash-and-burn methods of farming that impacted the region’s biodiversity.
Let’s have a look at the study area which over laps with the western reaches of Gorongosa National Park 1. We use folium
to visualize both the study area and the contours of the nationapark, like so:
%run ./src/download_path.py
= (
BBOX 33.9493075668371915,
-19.0773671765950361,
34.3448644146108251,
-18.6512837222308221,
)
= box(*BBOX)
area_of_interest
map = folium.Map(
=True,
max_bounds=[area_of_interest.centroid.y, area_of_interest.centroid.x],
location=False,
scrollWheelZoom
)
# bounds of area of interest
="Area of Interest", color="red").add_to(
folium.GeoJson(mapping(area_of_interest), namemap
)
= "https://git.geo.tuwien.ac.at/public_projects/environmental-remote-sensing"
url = shapefile.Reader(
park + "/-/raw/main/mozambique/corongosa.shp?ref_type=heads&inline=false"
url # noqa
)
# Corongosa Nationalpark
="Area of Interest").add_to(map)
folium.GeoJson(mapping(park), name
map
12.4 Copernicus Vegetation Data
Let us start by having a look at monthly aggregated LAI, fCOVER, fAPAR derived from Sentinel-3 over the study area. The data is divided further for three selected years; 2015, 2020, and 2025 (up to the present). We will loaad the data with the following URL.
= make_url("harmonised-vegetation-300m.zarr.zip", zip="True") # noqa url
https://git.geo.tuwien.ac.at/api/v4/projects/1266/repository/files/harmonised-vegetation-300m.zarr.zip/raw?ref=main&lfs=true
Loading the data is straightforward when using xarray
. For convenience we will transform the LAI values to range between 0 and 1 to make it more easily comparable with the other indices when plotting the data.
= xr.open_dataset(
ds
url,="zarr",
engine=False,
consolidated
).compute()"LAI"] = ds.LAI / 6
ds[ ds
<xarray.Dataset> Size: 17MB Dimensions: (lat: 144, lon: 134, year: 3, month: 12) Coordinates: * lat (lat) float64 1kB -18.65 -18.65 -18.66 ... -19.07 -19.07 -19.08 * lon (lon) float64 1kB 33.95 33.95 33.96 33.96 ... 34.34 34.34 34.35 * month (month) int64 96B 1 2 3 4 5 6 7 8 9 10 11 12 spatial_ref int32 4B 4326 * year (year) int64 24B 2015 2020 2025 Data variables: FAPAR (lat, lon, year, month) float64 6MB 0.74 0.816 ... nan nan FCOVER (lat, lon, year, month) float64 6MB 0.924 0.928 ... nan nan LAI (lat, lon, year, month) float64 6MB 0.6944 0.8389 ... nan nan Attributes: grid_mapping: crs valid_range: [0, 210] long_name: Leaf Area Index 333m standard_name: leaf_area_index units:
Now we plot the results with hvplot
. This creates an interactive plot whereby we can map the vegetation indices on an Open Street Map (OSM) and scroll through all months for the selected years; 2015, 2020, and 2025.
"variable").hvplot.image(
ds.to_array(="lon",
x="lat",
y=["year", "month", "variable"],
groupby=ccrs.PlateCarree(),
crs=True,
tiles="greens",
cmap=(0, 1),
clim=700,
frame_width )
If one scrolls throught the months once can easily spot the changes in vegetation cover for wet and dry seasons.
12.5 Timeseries of Vegetation Change
We can also show the seasonal change by aggregating all data for each timeslice and plot it as a line. Here one can see that vegetation follows a cyclical pattern from wet to dry seasons.
= hv.Layout(
p
[
ds[i]"lat", "lon"))
.median((
.hvplot.line(="month",
x="year",
by=i,
title=800,
frame_width
)for i in list(ds.variables.keys())[:3]
]1)
).cols( p
12.6 Anomalous Vegetation Change
By plotting the different years on top of each other, we can see that there also differences in the cyclical pattern and amplitude. For example some times it appears that the wet season starts later than other years. Also we can denote differences in the maximum amplitude of change, where especially 2025 appears to displays lower values for all variables.
We can also display this another way by taking the maximum value for each pixel’s timeseries and plotting this as a box-whisker plot (assuming that the minimum is relatively constant). Here we see that there appears to be a significant change to lower values for all vegetation indices with each subsequent year. One exception to this pattern is fAPAR which is higher in 2020 when compared to 2015, but 2025 is again lower than both 2015 and 2020.
"variable", "vegetation").max("month").hvplot.box(
ds.to_array(="vegetation", by=["variable", "year"]
y )
UNEP-WCMC and IUCN (2025), Protected Planet: The World Database on Protected Areas (WDPA) and World Database on Other Effective Area-based Conservation Measures (WD-OECM) [Online], August 2025, Cambridge, UK: UNEP-WCMC and IUCN. Available at: www.protectedplanet.net.↩︎