This is an R Markdown notebook which illustrates how to read, subset, and process a Sentinel-2 multispectral image in R.
This notebook aims to help Geomatica Basica students at Universidad Nacional to get to know R geospatial functionalities.
Previously, I downloaded a Sentinel 2 image over Bogota from the GloVis website.
After uncompressing the file, the content of the downloaded file is:
First, clean the memory:
rm(list=ls())
Then, load the required libraries:
library(rgdal)
library(gdalUtils)
library(raster)
library(sf)
## Linking to GEOS 3.7.2, GDAL 2.4.2, PROJ 5.2.0
##
## Attaching package: 'sf'
## The following object is masked from 'package:gdalUtils':
##
## gdal_rasterize
library(sp)
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.2 ✓ purrr 0.3.4
## ✓ tibble 3.0.4 ✓ dplyr 1.0.2
## ✓ tidyr 1.1.0 ✓ stringr 1.4.0
## ✓ readr 1.3.1 ✓ forcats 0.5.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x tidyr::extract() masks raster::extract()
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
## x dplyr::select() masks raster::select()
#library(getSpatialData)
folder <- "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/"
(bandas <- list.files(folder))
## [1] "T18NWL_20200104T152639_B01.jp2" "T18NWL_20200104T152639_B02.jp2"
## [3] "T18NWL_20200104T152639_B03.jp2" "T18NWL_20200104T152639_B04.jp2"
## [5] "T18NWL_20200104T152639_B05.jp2" "T18NWL_20200104T152639_B06.jp2"
## [7] "T18NWL_20200104T152639_B07.jp2" "T18NWL_20200104T152639_B08.jp2"
## [9] "T18NWL_20200104T152639_B09.jp2" "T18NWL_20200104T152639_B10.jp2"
## [11] "T18NWL_20200104T152639_B11.jp2" "T18NWL_20200104T152639_B12.jp2"
## [13] "T18NWL_20200104T152639_B8A.jp2" "T18NWL_20200104T152639_TCI.jp2"
bandas[1]
## [1] "T18NWL_20200104T152639_B01.jp2"
(ruta_bandas <- paste0(folder, bandas))
## [1] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B01.jp2"
## [2] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B02.jp2"
## [3] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B03.jp2"
## [4] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B04.jp2"
## [5] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B05.jp2"
## [6] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B06.jp2"
## [7] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B07.jp2"
## [8] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B08.jp2"
## [9] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B09.jp2"
## [10] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B10.jp2"
## [11] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B11.jp2"
## [12] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B12.jp2"
## [13] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_B8A.jp2"
## [14] "./datos/S2B_MSIL1C_20200104T152639_N0208_R025_T18NWL_20200104T184611.SAFE/GRANULE/L1C_T18NWL_A014780_20200104T153054/IMG_DATA/T18NWL_20200104T152639_TCI.jp2"
The common way of reading raster files from R is using the raster library functionalities: - raster (for individual bands) - stack (for merged bands)
getGDALVersionInfo()
## [1] "GDAL 2.4.2, released 2019/06/28"
Older GDAL versions (i.e. GDAL <= 2.4.4) are not capable of reading JPEG-2000 files. Thus, we need to translate such files into the very-well-known GeoTiff format:
gdal_translate(ruta_bandas[2], "./datos/s2_bogota/B02.tif")
## NULL
gdal_translate(ruta_bandas[3], "./datos/s2_bogota/B03.tif")
gdal_translate(ruta_bandas[4], "./datos/s2_bogota/B04.tif")
gdal_translate(ruta_bandas[8], "./datos/s2_bogota/B08.tif")
After converting the jp2 files into tif files, the result is this:
ruta_tif <- "./datos/s2_bogota/"
(lista_tif <- paste0(ruta_tif, list.files(ruta_tif)))
## [1] "./datos/s2_bogota/B02.tif" "./datos/s2_bogota/B03.tif"
## [3] "./datos/s2_bogota/B04.tif" "./datos/s2_bogota/B08.tif"
Let’s create a raster stack using the raster library:
s2_stack <- stack(lista_tif)
s2_stack
## class : RasterStack
## dimensions : 10980, 10980, 120560400, 4 (nrow, ncol, ncell, nlayers)
## resolution : 10, 10 (x, y)
## extent : 499980, 609780, 490200, 6e+05 (xmin, xmax, ymin, ymax)
## crs : +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0
## names : B02, B03, B04, B08
## min values : 0, 0, 0, 0
## max values : 65535, 65535, 65535, 65535
We need a file for our area of interest. For this, we can use the geojson.io application to create a polygon. The corresponding data was stored in a local directory.
mosca <- st_read("./datos/mosquera.geojson")
## Reading layer `mosquera' from data source `/Users/ivanlizarazo/Documents/ivan/GB/notebooks/datos/mosquera.geojson' using driver `GeoJSON'
## Simple feature collection with 1 feature and 0 fields
## geometry type: POLYGON
## dimension: XY
## bbox: xmin: -74.24011 ymin: 4.636997 xmax: -74.17248 ymax: 4.703722
## CRS: 4326
(crs_s2 <- crs(s2_stack))
## CRS arguments:
## +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84
## +towgs84=0,0,0
Let’s reproject the mosca object into the same crs of the raster stack:
mosca2 <- st_transform(mosca, crs_s2)
Now, convert the simple feature into a spatial polygon data frame:
aoi <- as_Spatial(mosca2)
Now, subset the sentinel 2 stack to cover the area of interest:
s2_crop <- crop(s2_stack, aoi, filename = "./datos/s2_mosca.tif", overwrite=TRUE)
s2_crop
## class : RasterBrick
## dimensions : 737, 751, 553487, 4 (nrow, ncol, ncell, nlayers)
## resolution : 10, 10 (x, y)
## extent : 584280, 591790, 512590, 519960 (xmin, xmax, ymin, ymax)
## crs : +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0
## source : /Users/ivanlizarazo/Documents/ivan/GB/notebooks/datos/s2_mosca.tif
## names : s2_mosca.1, s2_mosca.2, s2_mosca.3, s2_mosca.4
## min values : 640, 472, 322, 450
## max values : 5428, 5884, 8544, 10184
s2_resc <- s2_crop * 0.0001
s2_resc
## class : RasterBrick
## dimensions : 737, 751, 553487, 4 (nrow, ncol, ncell, nlayers)
## resolution : 10, 10 (x, y)
## extent : 584280, 591790, 512590, 519960 (xmin, xmax, ymin, ymax)
## crs : +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0
## source : /private/var/folders/l_/1nv5l0dx78g_d3k3_t85lrjr0000gn/T/Rtmpq2Pl6n/raster/r_tmp_2020-10-22_101515_10366_83433.grd
## names : s2_mosca.1, s2_mosca.2, s2_mosca.3, s2_mosca.4
## min values : 0.0640, 0.0472, 0.0322, 0.0450
## max values : 0.5428, 0.5884, 0.8544, 1.0184
Let’s calculate the normalized difference vegetation index (NDVI):
s2_ndvi <- (s2_resc$s2_mosca.4 - s2_resc$s2_mosca.3)/(s2_resc$s2_mosca.4 + s2_resc$s2_mosca.3)
s2_ndvi
## class : RasterLayer
## dimensions : 737, 751, 553487 (nrow, ncol, ncell)
## resolution : 10, 10 (x, y)
## extent : 584280, 591790, 512590, 519960 (xmin, xmax, ymin, ymax)
## crs : +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0
## source : memory
## names : layer
## values : -0.2767748, 0.8522812 (min, max)
Let’s normalize the NDVI values into the 0-255 range:
normalize <- function(x) {
min <- raster::minValue(x)
max <- raster::maxValue(x)
return(255* (x - min) / (max - min))
}
s2_ndvi2 <- normalize(s2_ndvi)
s2_ndvi2
## class : RasterLayer
## dimensions : 737, 751, 553487 (nrow, ncol, ncell)
## resolution : 10, 10 (x, y)
## extent : 584280, 591790, 512590, 519960 (xmin, xmax, ymin, ymax)
## crs : +proj=utm +zone=18 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0
## source : memory
## names : layer
## values : 0, 255 (min, max)
rf <- writeRaster(s2_ndvi2, filename="./datos/ndvi_mosca.tif", format="GTiff", overwrite=TRUE)
library(leaflet)
library(RColorBrewer)
pal <- colorNumeric(c("red", "orange", "yellow", "green", "darkgreen"), values(s2_ndvi2),
na.color = "transparent")
leaflet() %>% addTiles() %>%
addRasterImage(s2_ndvi2, colors = pal, opacity = 0.8) %>%
addLegend(pal = pal, values = values(s2_ndvi2),
title = "NDVI from Sentinel 2 - 04.01.2020")
sessionInfo()
## R version 3.6.3 (2020-02-29)
## Platform: x86_64-apple-darwin15.6.0 (64-bit)
## Running under: macOS Mojave 10.14.6
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] RColorBrewer_1.1-2 leaflet_2.0.3.9000 forcats_0.5.0 stringr_1.4.0
## [5] dplyr_1.0.2 purrr_0.3.4 readr_1.3.1 tidyr_1.1.0
## [9] tibble_3.0.4 ggplot2_3.3.2 tidyverse_1.3.0 sf_0.9-6
## [13] raster_3.3-13 gdalUtils_2.0.3.2 rgdal_1.5-18 sp_1.4-4
##
## loaded via a namespace (and not attached):
## [1] httr_1.4.2 jsonlite_1.7.1 foreach_1.5.1 R.utils_2.10.1
## [5] modelr_0.1.6 assertthat_0.2.1 cellranger_1.1.0 yaml_2.2.1
## [9] pillar_1.4.6 backports_1.1.10 lattice_0.20-41 glue_1.4.2
## [13] digest_0.6.26 rvest_0.3.5 colorspace_1.4-1 htmltools_0.5.0
## [17] R.oo_1.24.0 pkgconfig_2.0.3 broom_0.5.6 haven_2.2.0
## [21] scales_1.1.1 generics_0.0.2 farver_2.0.3 ellipsis_0.3.1
## [25] withr_2.3.0 cli_2.1.0 magrittr_1.5 crayon_1.3.4
## [29] readxl_1.3.1 evaluate_0.14 R.methodsS3_1.8.1 fs_1.4.1
## [33] fansi_0.4.1 nlme_3.1-147 xml2_1.3.2 class_7.3-17
## [37] tools_3.6.3 hms_0.5.3 lifecycle_0.2.0 munsell_0.5.0
## [41] reprex_0.3.0 compiler_3.6.3 e1071_1.7-4 rlang_0.4.8
## [45] classInt_0.4-3 units_0.6-7 grid_3.6.3 iterators_1.0.13
## [49] rstudioapi_0.11 htmlwidgets_1.5.2 crosstalk_1.1.0.1 base64enc_0.1-3
## [53] rmarkdown_2.4.6 gtable_0.3.0 codetools_0.2-16 DBI_1.1.0
## [57] R6_2.4.1 lubridate_1.7.9 knitr_1.30 KernSmooth_2.23-17
## [61] stringi_1.5.3 Rcpp_1.0.5 vctrs_0.3.4 png_0.1-7
## [65] dbplyr_1.4.3 tidyselect_1.1.0 xfun_0.18