avatarMahyar Aboutalebi, Ph.D. 🎓

Summary

This content provides a step-by-step guide on downloading Sentinel-2 images in R using the recently introduced API structure available at https://dataspace.copernicus.eu/.

Abstract

The content is a detailed guide on how to download Sentinel-2 images in R using the recently introduced API structure available at https://dataspace.copernicus.eu/. The guide covers various steps, including installing and importing necessary libraries, filtering and submitting queries, creating access tokens, downloading metadata files, extracting URL paths, downloading "jp2" files, reading and clipping raster files, and plotting true color surface reflectance images. The guide also includes code snippets and images for better understanding. The content is useful for individuals who work with R for satellite image downloads and processing.

Bullet points

  • The content provides a step-by-step guide on downloading Sentinel-2 images in R using the recently introduced API structure available at https://dataspace.copernicus.eu/.
  • The guide covers various steps, including installing and importing necessary libraries, filtering and submitting queries, creating access tokens, downloading metadata files, extracting URL paths, downloading "jp2" files, reading and clipping raster files, and plotting true color surface reflectance images.
  • The guide includes code snippets and images for better understanding.
  • The content is useful for individuals who work with R for satellite image downloads and processing.

How to Download Sentinel-2 Images in R (Updated January 2024)

In this story, I’ll guide you through the process of downloading Sentinel-2 images in R using the recently introduced API structure available at https://dataspace.copernicus.eu/.

Table of Contents

  1. 🌟 Introduction
  2. 🔍 Install and Import the Libraries
  3. ⏳ Filter and Submit the Query
  4. 🔐 Create the Access Token
  5. 📥 Download the Metadata File (.xml)
  6. 📜 Read the Metadata File and Extract URL Path
  7. 📥 Download the “jp2” files
  8. 🛠️ Read the “jp2” files, Stack, and Clip it to AOI
  9. 📈 Plot the True Color of the Surface Reflectance Images
  10. 📚 References

🌟 Introduction

In July 2023, I published my first story on Medium, detailing the process of downloading Sentinel-2 images using R. Over time, this story gained many reads and became my most viewed article. However, a new API structure was introduced two or three months later, and obviously, the R script of that article was not useful anymore. Therefore, I wrote a new article on downloading Sentinel-2 images in Python in December 2023, which now holds the top spot in my list of most-viewed articles.

Realizing that many users might still be working with R for satellite image downloads, I wanted to update my original article. However, the first article still has valuable sections on adding satellite base maps to QGIS, drawing polygons, and saving them as shapefiles for use in scripts. To keep this content, I decided to leave the initial article unchanged and instead write a fresh one on downloading Sentinel-2 images in R with the updated date in the title.

I won’t repeat the steps for signing up on the new website and creating credentials, as I’ve described those steps in this post:

Instead, I’ll be focusing on converting the script I wrote in Python (Google Colab) to a script in R (R-studio). I hope you find this approach useful and enjoy these steps to download Sentinel-2 images for your AOI.

🔍 Install and Import the Libraries

Let’s start by installing these libraries if they haven’t been installed on your system yet:

# Install necessary packages if not already installed
install.packages("httr")
install.packages("jsonlite")
install.packages("dplyr")
install.packages("xml2")
install.packages("raster")
install.packages("sf")

and load them into the memory:

# Load required libraries
library(httr)
library(jsonlite)
library(dplyr)
library(xml2)
library(raster)
library(sf)

⏳ Filter and Submit the Query

To gain access to metadata and filter images based on dates and location, you don’t need to use your credentials. In this step, you only need to input the database URL and provide the required information, such as satellite name, product level (e.g., “S2MI2A” for surface reflectance), and the area of interest:

# Define the base URL for the Copernicus Data Space API
url_dataspace <- "https://catalogue.dataspace.copernicus.eu/odata/v1"

# Define filtering parameters
satellite <- "SENTINEL-2"
level <- "S2MSI2A"
cloud_cover_max <- 1

# Define Area of Interest (AOI) in point or polygon formats
#aoi_point <- "POINT(-120.9970 37.6393)"
aoi_polygon <- "POLYGON ((-121.0616 37.6391, -120.966 37.6391, -120.966 37.6987, -121.0616 37.6987, -121.0616 37.6391))"

# Define start and end dates for data acquisition
start_date <- "2023-11-01"
end_date <- "2023-11-10"
start_date_full <- paste0(start_date, "T00:00:00.000Z")
end_date_full <- paste0(end_date, "T00:00:00.000Z")

Next, pass the URL and that information to the query command to obtain the results:

# Construct the query to filter products based on specified parameters
query <- paste0(url_dataspace, "/Products?$filter=Collection/Name%20eq%20'", satellite, "'%20and%20Attributes/OData.CSC.StringAttribute/any(att:att/Name%20eq%20'productType'%20and%20att/OData.CSC.StringAttribute/Value%20eq%20'", level, "')%20and%20OData.CSC.Intersects(area=geography'SRID=4326;", URLencode(aoi_point), "')%20and%20ContentDate/Start%20gt%20", start_date_full, "%20and%20ContentDate/Start%20lt%20", end_date_full)
response <- GET(query)

The response will be a JSON file that needs to be converted to a dataframe. Next, we will filter the records that are “online” and available on the database server:

# Extract and process the JSON response
response_content <- content(response, "text", encoding = "UTF-8")
response_json <- fromJSON(response_content)
result <- as.data.frame(response_json$value)

# Filter records where 'Online' column is TRUE
result <- filter(result, Online == TRUE)

# Display the first 10 results
head(result, 10)
Dataframe of the available images from Nov 1st to Nov 10th

🔐 Create the Access Token

After obtaining a list of available images in the dataframe format, we can use our credentials to generate a token, which can later be used for downloading the image:

# Authentication for accessing secured resources
username = "Your Username"
password = "Your Password"

# Define authentication server URL
auth_server_url <- "https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token"

# Prepare authentication data
data <- list(
  "client_id" = "cdse-public",
  "grant_type" = "password",
  "username" = username,
  "password" = password
)

# Request access token
response <- POST(auth_server_url, body = data, encode = "form", verify = TRUE)
access_token <- fromJSON(content(response, "text", encoding = "UTF-8"))$access_token

📥 Download the metadata file (.xml)

After generating the token, the next step is to extract the complete URL path generated for each band from the metadata. Each image listed in the dataframe has its own metadata. In this article, the focus is on processing the third image in the list captured on Nov 5th, 2023 around 18:00 UTM. (S2A_MSIL2A_20231105T185551_N0509_R113_T10SFG_20231105T222401):

# Set the Authorization header using obtained access token
headers <- add_headers(Authorization = paste("Bearer", access_token))

# Extract product information for the third product in the list
product_row_id <- 3 # The third images in the list (Nov 5th, 2023)
product_id <- result[product_row_id, "Id"]
product_name <- result[product_row_id, "Name"]

# Create the URL for MTD file
url_MTD <- paste0(url_dataspace, "/Products(", product_id, ")/Nodes(", product_name, ")/Nodes(MTD_MSIL2A.xml)/$value")

# GET request for MTD file and handle redirects
response <- httr::GET(url_MTD, headers, config = httr::config(followlocation = FALSE))
print(response$status_code)

# Extract the final URL for MTD file
url_MTD_location <- response$headers[["Location"]]
print(url_MTD_location)

# Download the MTD file
file <- httr::GET(url_MTD_location, headers, config = httr::config(ssl_verifypeer = FALSE, followlocation = TRUE))

# Set working directory and save the MTD file
setwd("C:/Users/Project/Sentinel-2")
outfile <- file.path(getwd(), "MTD_MSIL2A.xml")
writeBin(content(file, "raw"), outfile)

If you open the MTD_MSIL2A.xml in your browser, you can see the URL path for each band listed there and we need to extract those in the next step:

📜 Read the Metadata File and Extract URL Path

After downloading the metafile, we will read and extract the URL paths highlighted in the snapshot. For this article, the focus is on displaying the satellite image in RGB mode (Band 4, 3, and 2, respectively). However, if you wish to download other bands, such as SWIR bands, feel free to modify the following code and retrieve additional URL paths:

# Load XML2 library for XML processing
library(xml2)

# Read the MTD XML file
tree <- read_xml(outfile)
root <- xml_root(tree)

# Get paths for individual bands in Sentinel-2 granule
band_path <- list()
for (i in 1:4) {
  band_path[[i]] <- strsplit(paste0(product_name, "/", xml_text(xml_find_all(root, ".//IMAGE_FILE"))[i], ".jp2"), "/")[[1]]
}

# Display band paths
for (band_node in band_path) {
  cat(band_node[2], band_node[3], band_node[4], band_node[5], band_node[6], "\n")
}

The URL paths will be:

📥 Download the “jp2” files

With the URLs path and the generated token, we can download each band separately into the desired directory. The following lines will download Bands 2, 3, 4, and 8, corresponding to Blue, Green, Red, and NIR, in “jp2” format:

# Build URLs for individual bands and download them
for (band_node in band_path) {
  url_full <- paste0(url_dataspace, "/Products(", product_id, ")/Nodes(", product_name, ")/Nodes(", band_node[2], ")/Nodes(", band_node[3], ")/Nodes(", band_node[4], ")/Nodes(", band_node[5], ")/Nodes(", band_node[6], ")/$value")
  print(url_full)
  
  # Perform GET request and handle redirects
  response <- GET(url_full, headers, config = httr::config(followlocation = FALSE))
  
  if (status_code(response) %in% c(301, 302, 303, 307)) {
    url_full_location <- response$headers$location
    print(url_full_location)
  }
  
  # Download the file
  file <- httr::GET(url_full_location, headers, config = httr::config(ssl_verifypeer = FALSE, followlocation = TRUE))
  print(status_code(file))
  
  # Save the product
  outfile <- file.path(getwd(), band_node[6])
  writeBin(content(file, "raw"), outfile)
  cat("Saved:", band_node[6], "\n")
}

These files will be downloaded and saved in your directory:

🛠️ Read the “jp2” files, Stack, and Clip to AOI

Before plotting the Sentinel-2 image, we clip the image for the Area of Interest (AOI). To do it, in the following lines, we will list the path of each band saved in our directory, read the projection of one of them, reproject our AOI to the same projection as the raster file, clip the raster file to our AOI, and stack the bands to create a single file with three bands (Blue, Green, and Red):

# Set the folder path
folder_path <- getwd()

# List all files with the ".jp2" extension
jp2_files <- list.files(path = folder_path, pattern = "\\.jp2$", ignore.case = TRUE, full.names = TRUE)

# Print the list of files
print(jp2_files)

# Extract Blue, Green, and Red bands based on their file names
blue_band  <- jp2_files[grep("_B02_", jp2_files)]
green_band <- jp2_files[grep("_B03_", jp2_files)]
red_band   <- jp2_files[grep("_B04_", jp2_files)]

# List the specific raster files
file_list <- c(blue_band, green_band, red_band)

# Define the AOI polygon
aoi_polygon <- "POLYGON ((-121.0616 37.6391, -120.966 37.6391, -120.966 37.6987, -121.0616 37.6987, -121.0616 37.6391))"

# Read and convert the AOI polygon to an sf object
aoi <- st_as_sfc(aoi_polygon)
#aoi <- st_read(aoi)

# Define the initial CRS for the AOI polygon (assuming it's in WGS84, EPSG:4326)
aoi <- st_set_crs(aoi, 4326)

# Read the first raster file to get its projection
raster_file <- raster(file.path(file_list[1]))
raster_projection <- projection(raster_file)

# Reproject the AOI polygon to match the raster projection
aoi_transformed <- st_transform(aoi, raster_projection)

# Read, clip, and stack the raster files
raster_list <- lapply(file_list, function(file) {
  raster_file <- raster(file)
  raster_clipped <- crop(raster_file, st_bbox(aoi_transformed))
  return(raster_clipped)
})

raster_stack <- stack(raster_list)

📈 Plot the True Color of the Surface Reflectance Image

Plotting can be done with one line of code:

# Plot the stacked raster
plot(raster_stack)

However, the output will be an individual plot for each band:

To fix this issue and display the image as a composite RGB image, we will read each band from the stacked file, normalize the bands by applying a gain factor, stack them again, and use the plotRGB function:

# Extract individual bands from the stacked raster
blue <- raster_stack[[1]]
green <- raster_stack[[2]] 
red <- raster_stack[[3]] 

# Set gain for better visualization
gain <- 3

# Normalize the bands and apply gain
blue_n <- clamp(blue * gain / 10000, 0, 1)
green_n <- clamp(green * gain / 10000, 0, 1)
red_n <- clamp(red * gain / 10000, 0, 1)

# Create an RGB composite
rgb_composite_n <- brick(red_n, green_n, blue_n)

# Plot the RGB composite
plotRGB(rgb_composite_n, scale = 1, stretch = "lin")

The resulting map will be a nice snapshot captured by Sentinel-2 over an urban area, surrounded by many clouds on November 5th, 2023:

A snapshot taken by Sentinel-2 on November 5th over an urban area

📚 References

https://documentation.dataspace.copernicus.eu/APIs/OData.html

  • 👏 Be sure to clap for this article if you find it enjoyable.
  • 📚 Hit that “Follow” button to stay updated on my latest posts.
  • 🤝 Your feedback and engagement fuel my passion for sharing meaningful content.
  • 📱 Also, connect with me on other platforms for more engaging content! LinkedIn, ResearchGate, Github, and Twitter.

You can explore my other stories through these links:

Data Visualization
Data Science
R
Remote Sensing
Computer Science
Recommended from ReadMedium