MODIS data pre-processing

In this chapter we download, pre-process and compute NDVI from MODIS data. These procedures are discussed in more detail here.

Area of interest

First we get the area of interest. Normally you would get this from a file (for example, a shapefile). See ?terra::vect for reading such files.

aoi <- agrodata::data_ibli("marsabit")
aoi
##  class       : SpatVector
##  geometry    : polygons
##  dimensions  : 58, 8  (geometries, attributes)
##  extent      : 36.18001, 38.96632, 1.261462, 4.452989  (xmin, xmax, ymin, ymax)
##  coord. ref. : +proj=longlat +datum=WGS84 +no_defs
##  names       : COUNTRY PROVINCE DISTRICT     DIVISION      LOCATION
##  type        :   <chr>    <chr>    <chr>        <chr>         <chr>
##  values      :   KENYA  EASTERN MARSABIT LOIYANGALANI      MT KULAL
##                  KENYA  EASTERN MARSABIT     GADAMOJI        BADASA
##                  KENYA  EASTERN MARSABIT   NORTH HORR BALESA RIRIBA
##  SUB_LOCATION IBLI_UNIT IBLI_ID
##         <chr>     <chr>   <num>
##        ARAPAL  MT KULAL     115
##        BADASA  GADAMOJI     112
##        BALESA    DUKANA     119

As you can see we have 58 polygons, each representing a “sub-location”. Let’s plot the polygons on top of a map of Kenya to see where they are.

library(terra)
ken <- geodata::gadm("KEN", level=1, path=".")
plot(ken, border="gray")
plot(ken[ken$NAME_1 == "Marsabit", ], col="beige", border="gray", add=TRUE)
lines(aoi, col="red")

image0

So we are in Northe Kenya. Also note that some polygons (sub-locations) are much smaller than others.

Get MODIS files

Here we show how to download and pre-process the data, but only for 1 month, to limit the download and processing needed. After that we use pre-processed data.

Set up the download directory

datadir <- "modis"
dir.create(datadir, FALSE)
# datadir

In this example we download data from 2010 only.

start <- "2010-01-01"
end <- "2010-01-07"

To download the data you need to provide the username and password for your (free) EOSDIS account. If you do not have an account, you can sign up here. Here we use passwords that are stored in a file that I read in below (sorry, we cannot show you the values).

up <- readRDS("../pwds.rds")
up <- up[up$service == "EOSDIS", ]

Now we are ready to get the data

library(luna)
fmod <- luna::getModis(product="MOD09A1", start, end, aoi, download=TRUE,
            path=datadir, username=up$user, password=up$pwd)
fmod
## [1] "modis/MOD09A1.A2009361.h21v08.061.2021149144347.hdf"
## [2] "modis/MOD09A1.A2010001.h21v08.061.2021150093948.hdf"
## [3] "modis/MOD09A1.A2009361.h21v08.006.2015198070255.hdf"
## [4] "modis/MOD09A1.A2010001.h21v08.006.2015198080805.hdf"

We only have four files here, each representing 8 days – just to illustrate the processing.

A single MODIS tile for extends well over the study area. We first crop the data to correspond to the study area, and then we remove bad pixels (clouds and shadow) and then .

Quality

To remove bad quality pixels from the selected files, we follow the same method described here. We specify a matrix in which each row has start and end of the quality assessment (QA) bits considered, and the values to be rejected (in the image).

from <- c(1,3,11,12)
to   <- c(2,6,11,14)
reject <- c("10,11", "1100,1101,1110,1111", "1", "000,110,111")
qmat <- cbind(from, to, reject)

We need to project the case study boundary to the sinusoidal coordinate reference system that the MODIS data are in.

prj <- "+proj=sinu +lon_0=0 +x_0=0 +y_0=0 +a=6371007.181 +b=6371007.181 +units=m +no_defs "
pols <- project(aoi, prj)

Monthly NDVI computation

We now compute NDVI for each month. First, produce a quality mask, mask out bad quality pixels, crop to the area of interest, and then compute NDVI.

We use a loop over all files (fmod).

for(i in seq_along(fmod)) {
  # Load a MODIS file
  r <- rast(fmod[i])
  # Generate quality mask (layer 12)
  quality_mask <- modis_mask(r[[12]], 16, qmat)
  #Select only red and NIR
  r <- r[[1:2]]
  names(r) <- c("red", "NIR")
  # remove low quality pixels
  r <- mask(r, quality_mask)
  # remove areas outside the AOI rectangle
  r <- crop(r, pols)
  # ensure that all values are between 0 and 1
  r <- clamp(r, 0, 1)
  # Compute NDVI
  ndvi <- (r$NIR - r$red)/(r$NIR + r$red)
  filename <- gsub(".hdf$", "_ndvi.tif", fmod[i])
  writeRaster(ndvi, filename=filename, overwrite=TRUE)
}