Mini-project: Regreening App × STAC

From restoration observations to environmental evidence

In this notebook, you will learn how to combine field data from the Regreening App with spatial raster data from the GGW STAC.

If you are new to R, or want extra practice, we encourage checking out Notebook 1 - Basics of R and analysis with climate datasets, or some of the other notebooks available on the Practicals Gallery of the K4GGWA Platform’s Evidence Hub.

You will:

  1. download your Regreening App project’s polygon data from the Data Reporting System (DRS);
  2. check and explore the data in R;
  3. turn the wkt geometry in the csv into spatial polygons;
  4. access rasters from the GGW STAC;
  5. extract raster values within your restoration polygons;
  6. make a simple visualisation and write a short interpretation.

1. Get your Regreening App data

Download your project data

Open the Regreening App Data Reporting System (DRS).

As a Project Manager (PM):

  1. Open the DRS.
  2. Select the project you want to analyse.
  3. Download the project data as a csv file.
  4. Save the csv somewhere easy to find (a project folder).

The exported file contains information about the restoration plot, trees recorded within it, management practices, uses, location and polygon geometry. The kinds of data available depend on the app module that was used.

A note about the geometry

The Plot Geometry column contains polygons stored as wkt (Well-Known Text).

For example:

POLYGON((41.0162303 11.7928467,
41.0161877 11.7928214,
41.0161457 11.7928436,
41.0160885 11.7928373,
41.0162303 11.7928467))

The coordinates are stored as:

longitude latitude

We will turn these strings into spatial polygons using sf.


2. Set up R

library(tidyverse)
library(sf)
library(terra)
library(leaflet)
library(rstac)

Choose your downloaded csv:

dat <- read_csv("data.csv", show_col_types = FALSE)

glimpse(dat)
Rows: 184
Columns: 36
$ `Uploaded Date`                     <dttm> 2024-05-23 19:21:25, 2024-05-23 1…
$ `Date Collected`                    <date> 2024-05-23, 2024-05-23, 2026-04-2…
$ `Data Collector`                    <chr> "Abdu Ali", "Abdu Ali", "Girmay We…
$ `Project Name`                      <chr> "Regreening Africa", "Regreening A…
$ `Farmer/Group/Institution`          <chr> "Abdu Husein", "Abdu Husein", "gir…
$ Country                             <chr> "Ethiopia", "Ethiopia", "Ethiopia"…
$ `County/Region/Provence`            <chr> "Afar", "Afar", "Tigray", "Tigray"…
$ `Subcounty/District/commune/worede` <chr> "Samara", "Samara", "south east En…
$ `Plot Name`                         <chr> "2bab3532-cea9-4011-b699-a76e66903…
$ `Date FMNR Started`                 <date> 1987-05-01, 1987-05-01, 2026-04-0…
$ `Plot has Crops`                    <lgl> FALSE, FALSE, FALSE, TRUE, FALSE, …
$ `Crop Name`                         <chr> NA, NA, NA, "Tiff", NA, NA, NA, NA…
$ `Plot has fence`                    <lgl> TRUE, TRUE, TRUE, FALSE, TRUE, TRU…
$ `Fence type`                        <chr> "LIVE", "LIVE", "LIVE", "SOCIAL_FE…
$ `Other Fence type`                  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ `Plot Ownership type`               <chr> "INDIVIDUAL - CHURCH_OR_MOSQUE - C…
$ `Plot other ownership type`         <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ `Plot Size`                         <dbl> 1.35, 1.35, 0.50, 10.60, 0.12, 13.…
$ `Plot Size Unit`                    <chr> "Hectare", "Hectare", "Hectare", "…
$ `Plot Actual Size`                  <dbl> 0.00, 0.00, 0.03, 10.06, 0.18, 16.…
$ `Plot Geometry`                     <chr> "POLYGON((41.0162303 11.7928467,41…
$ `Local Name`                        <chr> "Burtukan", "Papaye", "seraw", "ac…
$ `Scientific Name`                   <chr> "Abutilon mauritianum", "Acacia ab…
$ `Management Practices`              <chr> "COMPOST_MANURE COPPICING WEEDING …
$ `Other Managements`                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ `Tree Usage`                        <chr> "NUTS FODDER SOIL_FERTILITY LEAF_V…
$ `Other Usages`                      <chr> NA, NA, NA, NA, NA, "fencing", NA,…
$ Height                              <chr> "LESS_1_5", "BETWEEN_1_5_AND_3", "…
$ `Actual Height`                     <chr> "50.00", "None", "3.50", "5.00", "…
$ `RCC/CBH`                           <chr> "1.35", "1.35", "6.00", "70.00", "…
$ Latitude                            <chr> "11.792856", "11.79289", "13.47751…
$ Longitude                           <chr> "41.016057", "41.015948", "39.4550…
$ Altitude                            <chr> "417.318", "417.508", "2137.500", …
$ Accuracy                            <chr> "4.17", "3.90", "10.48", "1.87", "…
$ Photo                               <chr> "https://radrs.icraf.org/uploaded/…
$ Comment                             <chr> "Very important of this trees", "A…
# Let's do a quick data check

dim(dat)
[1] 184  36
dat |>
  summarise(
    records = n(),
    unique_plots = n_distinct(`Plot Name`),
    unique_species = n_distinct(`Scientific Name`, na.rm = TRUE),
    missing_geometry = sum(is.na(`Plot Geometry`))
  )
# A tibble: 1 × 4
  records unique_plots unique_species missing_geometry
    <int>        <int>          <int>            <int>
1     184           52             92                0

A single restoration plot can contain multiple tree records, so the number of rows in the csv may be greater than the number of unique polygons.

Check this:

dat |>
  count(`Plot Name`) |>
  slice_head(n = 10)
# A tibble: 10 × 2
   `Plot Name`                              n
   <chr>                                <int>
 1 0084a325-00da-41c6-a8b0-8ab2914a594b     2
 2 02db3658-0625-4124-90f7-8e6ff972f113     5
 3 0679f72b-8b63-400c-a59d-8872fbd0f09a     1
 4 074bba73-b9d7-4b7e-82cf-05cdb81dacda     2
 5 0fed5cd8-0658-43bf-b4a1-a00eee6bec4d     1
 6 2a9db6e0-deb1-4103-ad72-2f9304b8386d     2
 7 2af52e21-6ad6-46ae-95fd-9a05d23c4d22     1
 8 2b1ae5df-3a87-4299-ac13-08b2ab361436     7
 9 2bab3532-cea9-4011-b699-a76e66903f4e     2
10 2c63b49b-7183-45a9-8dc8-29eb7c23e674     3

This distinction matters when calculating plot-level statistics.


3. Explore the restoration data

Which tree species have been recorded?

Try a simple bar chart of the most frequently recorded species.

dat |>
  filter(!is.na(`Scientific Name`)) |>
  count(`Scientific Name`, sort = TRUE) |>
  slice_head(n = 20) |>
  ggplot(aes(x = reorder(`Scientific Name`, n), y = n)) +
  geom_col() +
  coord_flip() +
  labs(
    title = "Most frequently recorded tree species",
    x = NULL,
    y = "Number of records"
  ) +
  theme_minimal()

How does the data look?

Often, since we are working with citizen-science data, there may be spelling errors, duplications, or other errors we should be aware of, and manage appropriately.


Which management practices are being used?

The Management Practices field can contain several practices in one cell, for example:

PRUNING FENCING WEEDING

We can separate these into individual practices.

management <- dat |>
  filter(!is.na(`Management Practices`)) |>
  separate_rows(`Management Practices`, sep = " ") |>
  count(`Management Practices`, sort = TRUE)

management
# A tibble: 13 × 2
   `Management Practices`     n
   <chr>                  <int>
 1 PRUNING                  165
 2 FENCING                   64
 3 THINNING                  48
 4 COMPOST_MANURE            47
 5 WEEDING                   45
 6 WATERING                  44
 7 MULCHING                  33
 8 COPPICING                 25
 9 POLLARDING                25
10 FIRE                      16
11 CHEMICAL_FERTILIZER       13
12 OTHER                      2
13 None                       1

Visualise the most common practices:

management |>
  slice_head(n = 10) |>
  ggplot(aes(
    x = reorder(`Management Practices`, n),
    y = n
  )) +
  geom_col() +
  coord_flip() +
  labs(
    title = "Recorded management practices",
    x = NULL,
    y = "Number of records"
  ) +
  theme_minimal()

Mini-question

Write down one observation from your exploratory analysis.

For example:

Pruning appears frequently in the monitoring records, while several other practices occur much less often.

Then ask:

Is this pattern a real difference in management, or could it partly reflect how data were collected?


4. Turn the WKT into polygons

Now we move from ordinary tabular data to spatial data.

First, keep one record for each unique restoration plot:

plots <- dat |>
  distinct(`Plot Name`, `Plot Geometry`, .keep_all = TRUE) |>
  filter(!is.na(`Plot Geometry`))

Convert the WKT geometry to an sf object:

plots_sf <- st_as_sf(
  plots,
  wkt = "Plot Geometry",
  crs = 4326
)

plots_sf
Simple feature collection with 52 features and 35 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 38.95715 ymin: 11.79282 xmax: 41.01623 ymax: 13.69444
Geodetic CRS:  WGS 84
# A tibble: 52 × 36
   `Uploaded Date`     `Date Collected` `Data Collector`   `Project Name`   
 * <dttm>              <date>           <chr>              <chr>            
 1 2024-05-23 19:21:25 2024-05-23       Abdu Ali           Regreening Africa
 2 2026-04-29 12:24:10 2026-04-29       Girmay Welay       Regreening Africa
 3 2026-05-05 16:51:29 2026-05-05       Abrah Hadgu        Regreening Africa
 4 2026-05-16 18:16:50 2026-05-14       Haftu Tsegy        Regreening Africa
 5 2026-05-18 11:38:05 2026-05-18       Gebremedhn Hagos   Regreening Africa
 6 2026-05-20 18:45:06 2026-05-20       Goitom Haileslasie Regreening Africa
 7 2026-05-20 19:13:59 2026-05-20       girmay welay       Regreening Africa
 8 2026-05-21 18:23:43 2026-05-21       Azab Araya         Regreening Africa
 9 2026-05-21 19:50:23 2026-05-21       girmay welay       Regreening Africa
10 2026-05-20 18:28:51 2026-05-21       Shishay Hagos      Regreening Africa
# ℹ 42 more rows
# ℹ 32 more variables: `Farmer/Group/Institution` <chr>, Country <chr>,
#   `County/Region/Provence` <chr>, `Subcounty/District/commune/worede` <chr>,
#   `Plot Name` <chr>, `Date FMNR Started` <date>, `Plot has Crops` <lgl>,
#   `Crop Name` <chr>, `Plot has fence` <lgl>, `Fence type` <chr>,
#   `Other Fence type` <chr>, `Plot Ownership type` <chr>,
#   `Plot other ownership type` <chr>, `Plot Size` <dbl>, …

Check the geometries:

sum(!st_is_valid(plots_sf))
[1] 7
st_crs(plots_sf)
Coordinate Reference System:
  User input: EPSG:4326 
  wkt:
GEOGCRS["WGS 84",
    ENSEMBLE["World Geodetic System 1984 ensemble",
        MEMBER["World Geodetic System 1984 (Transit)"],
        MEMBER["World Geodetic System 1984 (G730)"],
        MEMBER["World Geodetic System 1984 (G873)"],
        MEMBER["World Geodetic System 1984 (G1150)"],
        MEMBER["World Geodetic System 1984 (G1674)"],
        MEMBER["World Geodetic System 1984 (G1762)"],
        MEMBER["World Geodetic System 1984 (G2139)"],
        MEMBER["World Geodetic System 1984 (G2296)"],
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]],
        ENSEMBLEACCURACY[2.0]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    USAGE[
        SCOPE["Horizontal component of 3D system."],
        AREA["World."],
        BBOX[-90,-180,90,180]],
    ID["EPSG",4326]]

See your restoration plots

leaflet(plots_sf) |>
  addProviderTiles("OpenStreetMap.Mapnik") |>
  addPolygons(
    weight = 2,
    fillOpacity = 0.3
  )

You should now be able to see the polygons you collected in the field.


5. Connect to the GGW STAC

The GGW STAC provides access to spatial raster datasets that can be used alongside the restoration observations.

stac_source <- stac("https://stacapi100.thegrit.earth/")

stac_source
###rstac_query
- url: https://stacapi100.thegrit.earth/
- params:
- field(s): version, base_url, endpoint, params, verb, encode

List the available collections:

collections_query <- stac_source |>
  rstac::collections()

available_collections <- rstac::get_request(collections_query)

available_collections
###Collections
- collections (13 item(s)):
  - ls8_250_Africa
  - eswatini
  - flood_analysis_ggw
  - tree_suitability_africa_1km
  - africa_climate
  - africa_soil_maps_250m
  - srilanka_landhealth
  - ggw
  - somalia
  - harmony_in_habitat
  - ukpact_landhealth
  - SRTM
  - twende_landhealth
- field(s): collections, links

The STAC catalogue contains datasets that can provide environmental context for restoration sites.


6. Find raster data around your project

We can use the extent of our restoration polygons to define a bounding box.

project_bbox <- st_bbox(plots_sf)

project_bbox
    xmin     ymin     xmax     ymax 
38.95715 11.79282 41.01623 13.69444 

Convert it to the order expected by STAC:

bbox <- c(
  project_bbox["xmin"],
  project_bbox["ymin"],
  project_bbox["xmax"],
  project_bbox["ymax"]
)

bbox
    xmin     ymin     xmax     ymax 
38.95715 11.79282 41.01623 13.69444 

Now we can search the STAC.

For example, the reference STAC notebook uses the africa_soil_maps_250m collection:

items_query <- rstac::stac_search(
  q = stac_source,
  collections = "africa_soil_maps_250m",
  bbox = bbox,
  limit = 20
)

available_items <- rstac::get_request(items_query)

available_items
###Items
- matched feature(s): 8
- features (8 item(s) / 0 not fetched):
  - 20b98b663dce4a51bf81127b2c096e6a
  - 215917e3cf4d4e32a2206e5fec3cc4c8
  - 6b60d0bae25841398ac8c7ea5c04ee77
  - 7726efadd9ac48ef8ca804a7a7ad70c5
  - 92da9f22bec64b1587de76efb4b81460
  - c1fc237fd08e4a249277bae7069211ff
  - cbc0ff0331d8465789d323c5b7af9929
  - d4568b525a804a74b6be8d7b8b6845d3
- assets: layer, thumbnail
- item's fields: 
assets, bbox, collection, geometry, id, links, properties, stac_version, type

You can inspect the available items/assets:

for (item in available_items$features) {
  print(names(item$assets))
}
[1] "layer"     "thumbnail"
[1] "layer"     "thumbnail"
[1] "layer"     "thumbnail"
[1] "layer"     "thumbnail"
[1] "layer"     "thumbnail"
[1] "layer"     "thumbnail"
[1] "layer"     "thumbnail"
[1] "layer"     "thumbnail"

7. Read a raster from the STAC

For this exercise we will use the Soil Organic Carbon raster from the K4GGWA STAC.

The COG URL used in the reference notebook is:

soc_url <- "https://stacapi100.thegrit.earth/eodata/cogeo/ggw/Soil_Organic_Carbon_2023_GGW_500m.tif"

A Cloud Optimised GeoTIFF (COG) can be accessed remotely without downloading the entire raster.

Create a SpatRaster:

soc <- terra::rast(
  paste0("/vsicurl/", soc_url)
)

soc
class       : SpatRaster
size        : 5319, 14584, 1  (nrow, ncol, nlyr)
resolution  : 0.004491576, 0.004491576  (x, y)
extent      : -17.5441, 47.96105, 3.395632, 27.28633  (xmin, xmax, ymin, ymax)
coord. ref. : lon/lat WGS 84 (EPSG:4326)
source      : Soil_Organic_Carbon_2023_GGW_500m.tif
name        : Soil_Organic_Carbon_2023_GGW_500m
min value   :                             -9999
max value   :                             12759

Check its basic properties:

list(
  crs = crs(soc),
  resolution = res(soc),
  extent = ext(soc),
  layers = nlyr(soc)
)
$crs
[1] "GEOGCRS[\"WGS 84\",\n    ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n        MEMBER[\"World Geodetic System 1984 (Transit)\"],\n        MEMBER[\"World Geodetic System 1984 (G730)\"],\n        MEMBER[\"World Geodetic System 1984 (G873)\"],\n        MEMBER[\"World Geodetic System 1984 (G1150)\"],\n        MEMBER[\"World Geodetic System 1984 (G1674)\"],\n        MEMBER[\"World Geodetic System 1984 (G1762)\"],\n        MEMBER[\"World Geodetic System 1984 (G2139)\"],\n        MEMBER[\"World Geodetic System 1984 (G2296)\"],\n        ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n            LENGTHUNIT[\"metre\",1]],\n        ENSEMBLEACCURACY[2.0]],\n    PRIMEM[\"Greenwich\",0,\n        ANGLEUNIT[\"degree\",0.0174532925199433]],\n    CS[ellipsoidal,2],\n        AXIS[\"geodetic latitude (Lat)\",north,\n            ORDER[1],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n        AXIS[\"geodetic longitude (Lon)\",east,\n            ORDER[2],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n    USAGE[\n        SCOPE[\"Horizontal component of 3D system.\"],\n        AREA[\"World.\"],\n        BBOX[-90,-180,90,180]],\n    ID[\"EPSG\",4326]]"

$resolution
[1] 0.004491576 0.004491576

$extent
SpatExtent : -17.544097498854256, 47.961053019141261, 3.3956317739717932, 27.286326755130467 (xmin, xmax, ymin, ymax)

$layers
[1] 1

8. Overlay the restoration plots and raster

Before extracting raster values, make sure the polygons and raster use compatible coordinate reference systems.

plots_vect <- terra::vect(plots_sf)

plots_vect <- terra::project(
  plots_vect,
  soc
)

crs(plots_vect)
[1] "GEOGCRS[\"WGS 84\",\n    ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n        MEMBER[\"World Geodetic System 1984 (Transit)\"],\n        MEMBER[\"World Geodetic System 1984 (G730)\"],\n        MEMBER[\"World Geodetic System 1984 (G873)\"],\n        MEMBER[\"World Geodetic System 1984 (G1150)\"],\n        MEMBER[\"World Geodetic System 1984 (G1674)\"],\n        MEMBER[\"World Geodetic System 1984 (G1762)\"],\n        MEMBER[\"World Geodetic System 1984 (G2139)\"],\n        MEMBER[\"World Geodetic System 1984 (G2296)\"],\n        ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n            LENGTHUNIT[\"metre\",1]],\n        ENSEMBLEACCURACY[2.0]],\n    PRIMEM[\"Greenwich\",0,\n        ANGLEUNIT[\"degree\",0.0174532925199433]],\n    CS[ellipsoidal,2],\n        AXIS[\"geodetic latitude (Lat)\",north,\n            ORDER[1],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n        AXIS[\"geodetic longitude (Lon)\",east,\n            ORDER[2],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n    USAGE[\n        SCOPE[\"Horizontal component of 3D system.\"],\n        AREA[\"World.\"],\n        BBOX[-90,-180,90,180]],\n    ID[\"EPSG\",4326]]"
crs(soc)
[1] "GEOGCRS[\"WGS 84\",\n    ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n        MEMBER[\"World Geodetic System 1984 (Transit)\"],\n        MEMBER[\"World Geodetic System 1984 (G730)\"],\n        MEMBER[\"World Geodetic System 1984 (G873)\"],\n        MEMBER[\"World Geodetic System 1984 (G1150)\"],\n        MEMBER[\"World Geodetic System 1984 (G1674)\"],\n        MEMBER[\"World Geodetic System 1984 (G1762)\"],\n        MEMBER[\"World Geodetic System 1984 (G2139)\"],\n        MEMBER[\"World Geodetic System 1984 (G2296)\"],\n        ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n            LENGTHUNIT[\"metre\",1]],\n        ENSEMBLEACCURACY[2.0]],\n    PRIMEM[\"Greenwich\",0,\n        ANGLEUNIT[\"degree\",0.0174532925199433]],\n    CS[ellipsoidal,2],\n        AXIS[\"geodetic latitude (Lat)\",north,\n            ORDER[1],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n        AXIS[\"geodetic longitude (Lon)\",east,\n            ORDER[2],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n    USAGE[\n        SCOPE[\"Horizontal component of 3D system.\"],\n        AREA[\"World.\"],\n        BBOX[-90,-180,90,180]],\n    ID[\"EPSG\",4326]]"

Now extract the raster values within each restoration polygon.

soc_stats <- terra::extract(
  soc,
  plots_vect,
  fun = mean,
  na.rm = TRUE
)

head(soc_stats)
  ID Soil_Organic_Carbon_2023_GGW_500m
1  1                               752
2  2                              1046
3  3                              2030
4  4                              1885
5  5                              2372
6  6                              2039

Each polygon now has a summary of the raster values found inside it.


9. Your first raster + vector analysis

Join the extracted values back to the plot data:

plots_with_soc <- plots_sf |>
  mutate(
    SOC_mean = soc_stats[, 2]
  )

plots_with_soc |>
  st_drop_geometry() |>
  select(
    `Plot Name`,
    `Scientific Name`,
    `Management Practices`,
    SOC_mean
  ) |>
  head()
# A tibble: 6 × 4
  `Plot Name`                  `Scientific Name` `Management Practices` SOC_mean
  <chr>                        <chr>             <chr>                     <dbl>
1 2bab3532-cea9-4011-b699-a76… Abutilon mauriti… COMPOST_MANURE COPPIC…      752
2 e920412d-6c4a-491a-b1be-05c… Acacia abyssinica WATERING PRUNING COMP…     1046
3 0679f72b-8b63-400c-a59d-887… Acacia abyssinica COPPICING PRUNING WEE…     2030
4 86688712-7781-48d0-a04d-d4c… acaciaetbaicatum  PRUNING FENCING            1885
5 86d496c6-3622-4a62-ac9e-97b… Acacia abyssinica PRUNING                    2372
6 3db125ef-5401-47ce-a86b-5bc… delonix regia     FENCING PRUNING            2039

Challenge 1 — Describe your restoration landscape

Make a histogram of the mean SOC value across your restoration plots.

plots_with_soc |>
  st_drop_geometry() |>
  ggplot(aes(x = SOC_mean)) +
  geom_histogram(bins = 20) +
  labs(
    title = "Soil organic carbon across restoration plots",
    x = "Mean soil organic carbon",
    y = "Number of restoration plots"
  ) +
  theme_minimal()

Write a short interpretation

In 2–3 sentences:

What range of environmental conditions do the restoration plots occupy?

Are most plots clustered around similar values, or is there substantial variation?


10. Challenge 2 — Compare environmental conditions between plots

Now explore whether restoration plots with different characteristics occur under different environmental conditions.

For example, compare plots with and without crops.

plots_with_soc |>
  st_drop_geometry() |>
  ggplot(
    aes(
      x = `Plot has Crops`,
      y = SOC_mean
    )
  ) +
  geom_boxplot() +
  labs(
    title = "Soil organic carbon by cropping status",
    x = "Plot has crops",
    y = "Mean soil organic carbon"
  ) +
  theme_minimal()

Interpret carefully

You are comparing observed environmental conditions, not demonstrating that crops caused differences in SOC.

Write:

What pattern do you see?

What are two possible explanations?

What additional information would you need before making a causal claim?


11. Challenge 3 — Map an environmental variable

A map can often communicate the relationship between restoration activities and environmental conditions more effectively than a table.

Map the restoration polygons and colour them according to their extracted SOC value.

pal <- colorNumeric(
  palette = "YlGn",
  domain = plots_with_soc$SOC_mean,
  na.color = "transparent"
)

leaflet(plots_with_soc) |>
  addProviderTiles("OpenStreetMap.Mapnik") |>
  addPolygons(
    fillColor = ~pal(SOC_mean),
    fillOpacity = 0.7,
    color = "white",
    weight = 1,
    popup = ~paste0(
      "<strong>Plot:</strong> ", `Plot Name`,
      "<br><strong>Mean SOC:</strong> ",
      round(SOC_mean, 2)
    )
  ) |>
  addLegend(
    pal = pal,
    values = ~SOC_mean,
    title = "Mean SOC"
  )

Your task

Modify the map so that it shows a different variable or a different raster from the K4GGWA STAC.

Possible questions include:

  • Do restoration plots occur across a wide range of soil conditions?
  • Are plots with crops concentrated in particular environmental conditions?
  • Do plots with fencing show a different spatial pattern?
  • Are particular tree species recorded in particular environmental settings?

12. Challenge 4 — Combine field observations with raster evidence

Choose one field variable and one environmental variable.

For example:

Field observation Environmental variable
Crops present Soil organic carbon
Fencing Soil organic carbon
Management practice Soil organic carbon
Tree species Soil organic carbon
Plot size Soil organic carbon

Create one clear figure.

For example, you could compare SOC between plots with and without fencing:

final_plot <- plots_with_soc |>
  st_drop_geometry() |>
  ggplot(
    aes(
      x = `Plot has fence`,
      y = SOC_mean
    )
  ) +
  geom_boxplot() +
  labs(
    title = "Environmental conditions across restoration plots",
    subtitle = "Soil organic carbon by fencing status",
    x = "Plot has fence",
    y = "Mean soil organic carbon"
  ) +
  theme_minimal()

final_plot

Save your figure:

ggsave(
  "regreening_stac_mini_project.png",
  final_plot,
  width = 8,
  height = 5,
  dpi = 300
)

13. Write your evidence note

Write 100–150 words answering:

What did you investigate?

What restoration characteristic did you examine?

What environmental conditions did you observe?

What did the STAC raster tell you about the landscape?

What pattern did you find?

Describe the main pattern in your figure.

What might explain it?

Suggest one or two plausible ecological, climatic or land-use explanations.

What can’t you conclude?

Remember:

An association between a restoration characteristic and a raster-derived environmental variable does not by itself demonstrate causation.

Your analysis is an exploratory use of spatial evidence.


14. Looking ahead: analysing change through time

The workflow becomes even more powerful when the STAC contains time-indexed raster data.

Instead of having one raster representing a single period, a STAC collection can contain observations for different dates or time intervals.

Conceptually:

Restoration polygon
       │
       ├── Raster: 2010
       │
       ├── Raster: 2015
       │
       ├── Raster: 2020
       │
       └── Raster: 2025

You can then extract the raster value for the same polygon at each time step.

For example:

Plot 1

2010     12.4
2015     13.1
2020     15.7
2025     17.2

This allows questions such as:

  • Has vegetation productivity changed inside restoration plots?
  • Has soil moisture changed?
  • Has land surface temperature changed?
  • How has a climate indicator changed before and after restoration?
  • Do different restoration approaches occur in areas with different environmental trajectories?

The STAC search can eventually use a time interval, for example:

items_query <- rstac::stac_search(
  q = stac_source,
  collections = "YOUR_TIME_ENABLED_COLLECTION",
  bbox = bbox,
  datetime = "2010-01-01/2025-12-31",
  limit = 100
)

You would then loop over the returned time-indexed items, extract values for the same polygons, and build a plot × time dataset.

Note

Coming soon: As more time-indexed raster products are added to the K4GGWA STAC, this workflow can be extended from describing current environmental conditions to tracking change within restoration areas over time.


15. Your final mini-project

Your final submission should contain:

1. A spatial dataset

Your Regreening App polygons joined with at least one STAC-derived environmental variable.

2. One polished visual

A map, boxplot, histogram, scatterplot or another clear figure.

3. A short evidence note

100–150 words explaining:

What did you investigate → What did you find → Why might it matter → What are the limitations?

4. One idea for using the result

Think beyond the notebook.

Could this analysis help you:

  • identify where restoration is taking place under particular environmental conditions?
  • compare projects or landscapes?
  • support project reporting?
  • identify areas for further monitoring?
  • communicate restoration progress?
  • develop a figure for a report or presentation?
  • create a short evidence story for colleagues or stakeholders?

Key takeaway

The important skill is not simply knowing how to make a map or run terra::extract().

It is learning to connect:

What was observed in the field

↓

Where it happened

↓

What environmental conditions occurred there

↓

What pattern the data show

↓

What that evidence can — and cannot — tell us

That is the foundation for using restoration monitoring data together with Earth observation and spatial environmental datasets.