This notebook illustrates how to access Sentinel-2 imagery using R. It is based on this vignette from Féret & de Boissieu (2026).
We will use preprocS2, a package dedicated to Sentinel-2 data handling, which provides access to Spatio Temporal Asset Catalogs (STAC) via R scripts. It includes wrapper functions for the rstac package. It allows downloading and preparing Sentinel-2 images for further processings using biodivMapR.
The STAC specification is a common language to describe geospatial information. STAC resources are available online from various institutional and private providers (such as Copernicus dataspace and Nicrosoft Planetary Computers). STAC catalogs provides access to imagery data via spatio-temporal queries. Hence, the first step is to define an area of interest (aoi), and a period or day of interest.
The default STAC collection is sentinel-2-l2a from the Microsoft Planetary Computer catalog. Before proceeding, it is necessary to understand some things about Sentinel-2 imagery. Make sure to take notes about Sentinel-2 spatial and spectral resolution – on your paper notebook.
We will also use the spinR package that computes spectral indices from raster data.
In addition, we will use terra for reading raster files and tidyterra for visualization.
First of all, we need to install several packages:
library preprocS2 - https://github.com/jbferet/preprocS2
library biodivMapR- https://github.com/jbferet/biodivMapR
library spinR - https://github.com/jbferet/spinR
Use the console to run the following code – one line at a time –:
## Do not run this code from here - Use the R console!!!
#install.packages("remotes")
#remotes::install_github('jbferet/preprocS2')
#remotes::install_github('cran/dissUtils')
#install.packages("spam", repos = "https://reinhardfurrer.r-universe.dev")
#remotes::install_github('jbferet/biodivMapR')
#install.packages("pak")
#pak::pak("gitlab::jbferet/spinR")
#install.packages("tidyterra")
Clean the memory and next, load the libraries:
# clean workspace
rm(list = ls(all=TRUE))
# load libraries
library(preprocS2) # install instructions: https://github.com/jbferet/preprocS2
library(biodivMapR) # install instructions: https://github.com/jbferet/biodivMapR
library(spinR) # install instructions: https://github.com/jbferet/spinR
library(terra)
library(tidyterra)
library(sf)
library(ggplot2)
Follow these instructions to create a GeoJSON file with your AOI:
Go to GeoJSON Studio,
Search the municipality in your department with higher agricultural production
Change the background to Satellite
Click on Draw to create a polygon with your area of interest
Save the AOI as GeoJSON in your data directory
Then, read the geojson file:
#use the function list.files() to check your directory and filenames
lebrija <- st_read("lebrija/map.geojson")
## Reading layer `map' from data source
## `/Users/ials/Documents/unal2025/GB2024/data/lebrija/map.geojson'
## using driver `GeoJSON'
## Simple feature collection with 1 feature and 1 field
## Geometry type: POLYGON
## Dimension: XY
## Bounding box: xmin: -73.36834 ymin: 7.012452 xmax: -73.20349 ymax: 7.133273
## Geodetic CRS: WGS 84
Now, let’s define input & output directories and files:
input_dir_vect <- './lebrija'
input_dir_rast <- './lebrija/sentinel-2'
aoi_path <- file.path(input_dir_vect, 'map.geojson')
A Sentinel-2 acquisition with minimal cloud cover needs to be identified. The Copernicus Browser is a useful service to explore the availability of Sentinel-2 acquisitions. Search images using your municipality’s name. In addition to spatio-temporal filters, it includes filters such as the maximum cloud cover.
We searched images acquired from January 2026 onwards. An acquisition from 17 January 2026 shows decent conditions.
The library preprocS2 allows downloading from multiple providers. Microsoft Planetary computer is currently the default option.
The function get_s2_raster downloads Sentinel-2 data corresponding to the spatio-temporal query with these parameters:
aoi_path: path for vector layer. Must include a unique polygon
datetime: date of acquisition, provided as a Date object.
stac_info: list including provider identifier (default = mpc)
output_dir: output directory where to store data
site_name: this will allow identifying rasters later
options: options including maximum cloud cover and higher level processing
Let’s try it:
# Change values as needed:
datetime <- as.Date('2026-01-17')
stac_info <- list('provider' = 'mpc')
site_name = 'lebrija'
options <- set_options_preprocS2(fun = 'get_s2_raster')
options$overwrite <- FALSE
list_files <- get_s2_raster(aoi_path = aoi_path,
datetime = datetime,
stac_info = stac_info,
output_dir = input_dir_rast,
site_name = site_name,
options = options)
## get S2 tiles corresponding to aoi
## download S2 collection
rast_path <- list_files$Refl_L2A # S2 L2A reflectance
mask_path <- list_files$vegetation_mask # S2 binary mask identifying vegetation, discarding clouds & shadows
After several minutes, the requested data should be in your output folder, organized on a similar structure as this one:
The collections/plot_001.rds corresponds to the item collection resulting from the spatiotemporal query. Use the R function readRDS to access the content of the file.
readRDS('./lebrija/sentinel-2/collections/plot_001.rds')
## ###Items
## - features (1 item(s)):
## - S2C_MSIL2A_20260117T152651_R025_T18NXN_20260117T183212
## - assets:
## AOT, B01, B02, B03, B04, B05, B06, B07, B08, B09, B11, B12, B8A, datastrip-metadata, granule-metadata, inspire-metadata, product-metadata, rendered_preview, safe-manifest, SCL, tilejson, visual, WVP
## - item's fields:
## assets, bbox, collection, geometry, id, links, properties, stac_extensions, stac_version, type
The raster_samples directory includes:
─ lebrija_001_2024-01-17.tiff: L2A (surface reflectance) Sentinel-2 data
─ lebrija_001_2026-01-17_BIN.tiff: binary mask derived from lebrija_001_2026-01-17_SCL.tiff and focusing on vegetation class.
─ lebrija_001_2026-01-17_BIN_v2.tiff: binary mask produced from alternative radiometric criteria, including NDVI mask to remove non vegetated pixels, Blue mask to remove hazy/cloudy pixels, and NIR mask to remove shaded pixels. You can adjust threshold for cloudMask (B02), shadeMask (B08), and NDVIMask. See here for additional information on how these masks work.
─ lebrija_001_2024-01-17_SCL.tiff: scene classification provided by sen2cor atmospheric correction.
s2_tiles_lebrija.rds contains the Sentinel-2 tile ID for the scene
s2_footprint_lebrija.gpkg contains the footprint of the Sentinel-2 tile ID
First, let’s read the Sentinel-2 imagery and know its metadata:
# 1. Load the Sentinel-2 bands (e.g., stacked into a multi-layer .tif)
# B4 = Red, B3 = Green, B2 = Blue for Sentinel-2
(s2_image <- rast("./lebrija/sentinel-2/raster_samples/lebrija_001_2026-01-17.tiff"))
## class : SpatRaster
## size : 1343, 1826, 10 (nrow, ncol, nlyr)
## resolution : 10, 10 (x, y)
## extent : 680190, 698450, 775440, 788870 (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 18N (EPSG:32618)
## source : lebrija_001_2026-01-17.tiff
## names : B02, B03, B04, B05, B06, B07, ...
## min values : 12, 38, -128, 22, -81, -82, ...
## max values : 14200, 14056, 14104, 14553, 14527, 14594, ...
# Calculate the mean for each layer
(layer_means <- global(s2_image, fun = c("mean", "min", "max"), na.rm = TRUE))
Note two things: (i) there are ten spectral bands; and (ii) the range of values per band is as higher as 15000.
# Apply a custom 1% linear stretch (ignores bottom 1% and top 1% of outliers)
s2_enhanced <- terra::stretch(s2_image,
minq = 0.01,
maxq = 0.99,
minv = 100,
maxv = 12500)
First, a true color composite:
# 2. True color RGB 432
ggplot() +
geom_spatraster_rgb(data = s2_enhanced, r = 3, g = 2, b = 1, max_col_value= 4800, stretch = "hist") +
theme_minimal() +
labs(title = "Sentinel-2 True Color Image")
## <SpatRaster> resampled to 500775 cells.
In your notebook, describe what land cover classes you see.
Now, a false color composite:
# 2. False color RGB B8 – B11 – B3
ggplot() +
geom_spatraster_rgb(data = s2_enhanced, r = 7, g = 9, b = 2, max_col_value= 6850, stretch = "hist") +
theme_minimal() +
labs(title = "Sentinel-2 Agricultural Composite")
## <SpatRaster> resampled to 500775 cells.
In your notebook, describe what land cover classes you see.
Write down three things you have learned today in relation to:
typical “natural” colors for several land cover classes (RGB432)
typical “false” colors for several land cover classes (RGB 8-11-3)
spatial resolution of Sentinel-2
Féret, J.-B., de Boissieu, F., 2019. biodivMapR: an R package for α‐ and β‐diversity mapping using remotely‐sensed images. Methods Ecol. Evol. 00:1-7. https://doi.org/10.1111/2041-210X.13310
Féret, J.-B., Asner, G.P., 2014. Mapping tropical forest canopy diversity using high-fidelity imaging spectroscopy. Ecol. Appl. 24, 1289–1296. https://doi.org/10.1890/13-1824.1