MODIS Vegetation Indices: a GEE Approach | by Willy Hagi | Towards Data Science

The Moderate Resolution Imaging Spectroradiometer (MODIS) is one of the key sensors used in Earth Observation ever since it was first launched into orbit in 1999. Several reasons can be appointed for its success: the high revisit-time of 1 to 2 days, the global coverage, the wide range of applicability and its derived products. Just to name a few.
In this tutorial, you’ll focus on the MOD13Q1 product, which consists of both the Normalized Difference Vegetation Index (NDVI) and the Enhanced Vegetation Index (EVI) in a global scale at a 16 days frequency. You might already know the NDVI from the Introduction to GEE, but here you’ll also explore the usefulness of EVI for environmental analysis.
From the GEE Introduction, you already know how to sign up and use gee, install folium for interactive visualization and geehydro to interact with the code in a similar way to the native Javascript API. If you’re interested in climate modeling and read the Introduction to CMIP6, then you must know Proplot as well. The novel package for this tutorial is ipygee, which works in a similar way as geehydro to allow the use of some functions that are only available within the GEE code editor.
To install ipygee, just use Pip:
pip install ipygee
Then you can proceed to import all the tools you’ll need in this tutorial:
import eeimport folium import geehydroimport numpy as np # yes, numpy!import pandas as pd # yes, pandas!import proplot as plot import matplotlib.pyplot as plt from ipygee import*from pandas.plotting import register_matplotlib_converters
As you know, to use gee you must always trigger the authentication and initialize the library, especially if you are interested in running the code in Google Colab.
# Trigger the authentication flow.>>> ee.Authenticate()# Initialize the library.>>> ee.Initialize()
The World Database on Protected Areas (WDPA) is the greatest database of marine and terrestrial protected areas and it’s easily available in GEE. It provides comprehensive geographic information and the boundaries of any area in the world. Here you’ll use it to get the shape of the Xingu Indigenous Park in Brazil, known as one of the first and greatest protected areas in the world.
With the EE snippet, you can load the polygon database of the WDPA and filter the area of the Xingu Park:
wdpa_polygon = ee.FeatureCollection('WCMC/WDPA/current/polygons')# select the Xingu Indigenous Park shapefilexingu = wdpa_polygon.filter(ee.Filter.eq('NAME', 'Parque do Xingu'))
As you already know, Folium and geehydro allow visualization of GEE data. In this case, you can plot the area of the Xingu Park and interact with it:
Map = folium.Map(location=[-12., -52.5], zoom_start=8)>>> Map.addLayer(xingu)>>> Map
The Xingu Park is a special place for its rich history and diversity. Before the boundaries were defined to make the Park official in 1961, the Villas-Boas brothers first went there almost by accident as part of the Roncador-Xingu expedition. As most of the country (and the government at the time) thought the region was deserted, they quickly found it was filled with hundreds of different indigenous tribes. Those encounters defined the beginning of the history of indigenous rights in Brazil.
To load the MODIS product, simply use ee.ImageColletion() with the snippet you need. To make the analysis a bit easier, you’ll filter the data for the last 3 years.
# MODIS MOD13Q1modis = ee.ImageCollection('MODIS/006/MOD13Q1')modis = modis.filterDate(ee.DateRange('2016-01-01','2019-12-01'))
The MOD13Q1 product has both the NDVI and EVI available, so you can easily select them and attribute the data to different variables:
# select EVI and NDVIevi = modis.select('EVI')ndvi = modis.select('NDVI')
A minor issue with MODIS data is that you need to apply a scale factor according to the product you’re using to make sense of it. According to this User Guide (RTFM), the scale factor for the MOD13Q1 product is simply 0.0001. That means you’ll have to multiply each dataset by this scale factor to start using it properly.
You probably already figured out that you’ll need to write a small function to do it. In GEE dialect that means you’ll have to .map() over an ImageCollection. No unnecessary for-loops here, please. When in Rome, do as the Romans do.
Please, insert your Monty Python joke here.
def scale_factor(image):# scale factor for the MODIS MOD13Q1 productreturn image.multiply(0.0001).copyProperties(image,                                              ['system:time_start'])# mapping function to multiply by the scale factorscaled_evi = evi.map(scale_factor)scaled_ndvi = ndvi.map(scale_factor)
With the datasets properly scaled, you can finally use them within the WDPA boundaries of the Xingu Park and start your analysis. A good way to start is to plot the mean NDVI on the interactive Map.
# mean NDVI in the Xingu Park>>> Map.addLayer(scaled_ndvi.mean().clip(xingu),                  vis_params={'min': 0,                              'max': 1,                              'palette': ['red', 'yellow','green']})>>> Map
The same code goes for the mean EVI. Thanks to Folium and geehydro it’s simple as that. While an interactive Map is cool, any python enthusiast would be interested to get a time series and squeeze out some cool information from it with data science. GEE has a native way to make time-series plotting but, unfortunately, that’s only available in the Code Editor. That’s exactly where ipygee comes in handy, as it mimics the native javascript-based ui.Chart.image.series(). Without it, we would be in trouble.
It works just the same way with the same arguments, and it even allows you to make a small widget from it when you plot.
# Xingu MOD13Q1 NDVI time seriesxingu_ndvi = chart.Image.series(**{'imageCollection': scaled_ndvi,                                   'region': xingu,                                   'reducer': ee.Reducer.mean(),                                   'scale': 1000,                                   'xProperty': 'system:time_start'})xingu_ndvi.renderWidget(width='50%')
That could get a way better, you could probably be thinking. The amazing part from that is that ipygee also permits you to work with this time series as any Dataframe you might be familiar with. That’s where Pandas gets in the pitch. Don’t believe it?
>>> type(xingu_ndvi.dataframe)pandas.core.frame.DataFrame
From then on, you can go from GEE to our usual Python ecosystem at ease. Think about Pandas, Seaborn or any related package you know to study time series. Thanks to ipygee you can go from both points any time you want.
That means you can plot it in the way you like it. Try it out with Proplot:
fig, ax = plot.subplots(figsize=(7, 3), tight=True)ax.plot(xingu_ndvi.dataframe.index, xingu_ndvi.dataframe['NDVI'],        color='forest green', marker='o')plot.show()
Again, the same can be done with the EVI time series just waiting around the corner. With a bit of Pandas, we can transform these 16-day sampled time series in a monthly time series with .groupby(). That could make them behave a bit less wildly.
# monthly averagingxingu_evi_monthly = xingu_evi.dataframe.groupby(pd.Grouper(freq="M")).mean()xingu_ndvi_monthly = xingu_ndvi.dataframe.groupby(pd.Grouper(freq="M")).mean()
To compare the two time series, you’ll go back to the standard Matplotlib as there are some issues about sharing axis in Proplot for the time. Unfortunately, or not, you’ll have to stick with a little less elegant solution until they fix this minor problem.
# time indextime = xingu_evi_monthly.index# plotfig, ax1 = plt.subplots(figsize=(7, 3))ax2 = ax1.twinx()# EVIax1.plot(time, xingu_evi_monthly, label='EVI',         color='muddy brown', marker='+')# NDVIax2.plot(time, xingu_ndvi_monthly, label='NDVI',         color='forest green', marker='o')ax1.set_xlabel('Time')ax1.set_ylabel('EVI')ax2.set_ylabel('NDVI')ax1.set_yticks(np.arange(0.3, 1.1, 0.1))ax2.set_yticks(np.arange(0.3, 1.1, 0.1))plt.legend()plt.tight_layout()plt.show()
Notice the difference in magnitude but the same behavior in both time series. It’s likely that the more complex algorithm of EVI makes it resistant to atmospheric effects and allows it to reflect more robust vegetation changes. The vegetation in the Xingu Park is dominated by the Cerrado biome, which is less dense than the standard Amazon rainforest. Perhaps that’s why EVI is lower than NDVI, but that’s a guess.
MODIS-related products are fantastic datasets that allow a wide range of environmental applications. The global, high-frequency and high-resolution MOD13Q1 dataset is extraordinarily useful to study vegetation behavior and changes. With the Python API, you can have access to many of these products in GEE and, hopefully, you learned how to explore them with this tutorial.
As usual, the interactive Jupyter Notebook with all of the code above is freely available in my Climate Data Science repository.