Monitoring Vegetation Health with Google Earth Engine: A Complete NDVI Analysis Tutorial

Learn how to automate vegetation health monitoring using Google Earth Engine and Sentinel-2 satellite imagery. This step-by-step tutorial shows you how to calculate NDVI, filter high-quality images, and automatically export 6 months of vegetation data to Google Drive—all with ~50 lines of code. Perfect for researchers, environmental scientists, and GIS enthusiasts looking to harness cloud-based Earth observation.
Google Earth Engine
Author

Mapcrafty Team

Published

October 7, 2025

Monitoring Vegetation Health with Google Earth Engine: A Complete NDVI Analysis Tutorial

Introduction

Vegetation monitoring is crucial for agriculture, environmental conservation, and climate research. In this tutorial, we’ll walk through a complete Google Earth Engine (GEE) workflow that analyzes vegetation health using NDVI (Normalized Difference Vegetation Index) from Sentinel-2 satellite imagery over a 6-month period in Germany.

What you’ll learn: - How to filter and process Sentinel-2 imagery - Calculate NDVI for vegetation analysis - Automate bulk exports to Google Drive - Visualize results on an interactive map


Understanding the Code: Step by Step

1. Setting Up the Time Range

var endDate = ee.Date(Date.now());
var startDate = endDate.advance(-6, 'month');
print('Start Date:', startDate);
print('End Date:', endDate);

What’s happening here: This section establishes our temporal window for analysis. We’re creating a 6-month period ending today: - ee.Date(Date.now()) captures the current date - advance(-6, 'month') moves backward 6 months from today - The print() statements display these dates in the Console for verification

Why 6 months? This timeframe captures seasonal variations in vegetation while keeping the dataset manageable.


2. Loading Sentinel-2 Imagery

var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  .filterBounds(aoi)
  .filterDate(startDate, endDate)
  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20));
print('Total images available:', s2.size());

Breaking it down: - 'COPERNICUS/S2_SR_HARMONIZED' - This is the Sentinel-2 Surface Reflectance collection, atmospherically corrected and ready for analysis - .filterBounds(aoi) - Filters images to only those intersecting our Area of Interest (AOI) in Germany - .filterDate(startDate, endDate) - Limits images to our 6-month window - .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20)) - Critical filter: Only keeps images with less than 20% cloud cover to ensure quality data

Why Surface Reflectance? SR data is already atmospherically corrected, making it suitable for time-series analysis and vegetation indices.

Image 1: Area of Interest in Germany

3. Calculating NDVI

function addNDVI(image) {
  var ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI');
  return image.addBands(ndvi)
    .copyProperties(image, ['system:time_start', 'system:index']);
}

var s2WithNDVI = s2.map(addNDVI);

Understanding NDVI: NDVI (Normalized Difference Vegetation Index) measures vegetation health using this formula:

NDVI = (NIR - Red) / (NIR + Red)

In Sentinel-2: - B8 = Near-Infrared band (NIR) - strongly reflected by healthy vegetation - B4 = Red band - absorbed by vegetation for photosynthesis

The function explained: 1. normalizedDifference(['B8', 'B4']) - Automatically calculates (B8-B4)/(B8+B4) 2. .rename('NDVI') - Names the new band for easy reference 3. .addBands(ndvi) - Adds NDVI as a new band to the original image 4. .copyProperties() - Preserves timestamp and metadata (essential for time-series)

NDVI Values interpretation: - -1 to 0: Water, bare soil, snow - 0 to 0.3: Sparse vegetation, stressed plants - 0.3 to 0.6: Moderate vegetation - 0.6 to 1: Dense, healthy vegetation


4. Automated Monthly Export Function

function getTwoImagesPerMonth() {
  for (var m = 0; m < 6; m++) {
    var monthStart = startDate.advance(m, 'month');
    var monthEnd = monthStart.advance(1, 'month');
    
    var monthImages = s2WithNDVI
      .filterDate(monthStart, monthEnd)
      .sort('CLOUDY_PIXEL_PERCENTAGE')
      .limit(2);

The strategy: This function intelligently selects the two clearest images per month over 6 months (up to 12 images total):

  1. Loop through 6 months (for (var m = 0; m < 6; m++))
  2. Define monthly boundaries for each iteration
  3. Filter images to that specific month
  4. Sort by cloud cover - clearest images first
  5. Limit to 2 images - balances temporal coverage with data volume

Why 2 images per month? This captures early and late-month conditions while avoiding data overload.


5. Export Configuration

var imageList = monthImages.toList(2);
var count = monthImages.size().getInfo();

print('Month ' + m + ': Found ' + count + ' images');

for (var i = 0; i < count; i++) {
  var img = ee.Image(imageList.get(i));
  var ndvi = img.select('NDVI').clip(aoi);
  
  var date = ee.Date(img.get('system:time_start'));
  var filename = ee.String('ndvi_aoi_germany_').cat(date.format('YYYY_MM_dd'));

Export preparation: - Converts the ImageCollection to a list for iteration - Gets the actual count of available images (might be less than 2) - Extracts only the NDVI band (not the full multi-band image) - Clips to AOI boundaries to reduce file size - Creates descriptive filenames with dates (e.g., ndvi_aoi_germany_2024_04_15)


6. Google Drive Export Tasks

Export.image.toDrive({
  image: ndvi,
  description: filename.getInfo(),
  folder: 'GEE_NDVI_Export',
  fileNamePrefix: filename.getInfo(),
  region: aoi,
  scale: 10,
  crs: 'EPSG:4326',
  maxPixels: 1e13,
  fileFormat: 'GeoTIFF'
});

Export parameters explained: - folder: 'GEE_NDVI_Export' - Creates/uses this folder in your Google Drive - scale: 10 - 10-meter resolution (Sentinel-2’s native resolution for visible/NIR bands) - crs: 'EPSG:4326' - WGS84 coordinate system (standard lat/lon) - maxPixels: 1e13 - Allows large exports (10 trillion pixels) - fileFormat: 'GeoTIFF' - Compatible with QGIS, ArcGIS, Python (rasterio), R

Important: These export tasks are queued, not executed. You must manually run them from the Tasks tab.

Image 2: Export to Drive Interface

7. Map Visualization

var ndviVis = {
  min: -1,
  max: 1,
  palette: ['blue', 'white', 'green']
};

Map.centerObject(aoi, 10);
Map.addLayer(aoi, {color: 'red'}, 'AOI');

var recentNDVI = s2WithNDVI
  .sort('system:time_start', false)
  .first()
  .select('NDVI')
  .clip(aoi);
  
Map.addLayer(recentNDVI, ndviVis, 'Most Recent NDVI');

Visualization setup: 1. Color palette: Blue (water/bare soil) → White (sparse vegetation) → Green (dense vegetation) 2. Center map on the AOI at zoom level 10 3. Display AOI boundary in red for reference 4. Show most recent NDVI - uses .sort('system:time_start', false) to get the latest image

Visual interpretation: When you run this code, you’ll see your AOI with vegetation displayed in green tones. Darker green indicates healthier, denser vegetation.


Practical Applications

This workflow can be adapted for:

Agriculture - Monitor crop growth stages - Detect irrigation issues - Assess drought stress

Forestry - Track deforestation - Monitor forest health - Assess wildfire recovery

Environmental Science - Study seasonal phenology - Assess wetland conditions - Monitor urban green spaces

Climate Research - Analyze vegetation response to climate events - Study land cover changes - Track ecosystem productivity


Tips for Success

  1. Define your AOI carefully - Use the geometry tools in GEE or import a shapefile
  2. Check the Console output - Verify you have enough images before exporting
  3. Adjust cloud threshold - If you get too few images, increase from 20% to 30%
  4. Monitor export limits - Google Earth Engine has daily export quotas
  5. Batch process exports - Run 3-4 tasks at a time to avoid timeouts

Next Steps

Once you have your NDVI GeoTIFFs exported, you can:

  • Time-series analysis in Python using rasterio and pandas
  • Create animations showing vegetation changes over time
  • Statistical analysis comparing different months or years
  • Machine learning for crop classification or anomaly detection

Conclusion

This Google Earth Engine workflow demonstrates the power of cloud-based Earth observation. With just a few dozen lines of code, you can process terabytes of satellite data, calculate vegetation indices, and export ready-to-analyze datasets—all without downloading raw imagery or managing local storage.

The beauty of GEE is its scalability: this same code works whether your AOI is 1 km² or 10,000 km². Just adjust the maxPixels parameter and let Google’s infrastructure handle the heavy lifting.

Ready to try it yourself? Head to Google Earth Engine Code Editor and start exploring!


Resources


📧 Contact & Collaboration

Have questions about this analysis or interested in collaborating on geospatial projects? We’d love to hear from you!

Get in touch with our research team: - Email: mapcrafty@gmail.com - Subject line: “Inquiry about Remote Sensing Applications for air quality Monitoring”

Whether you’re working on similar research, need technical consultation, or want to discuss potential collaborations in geospatial analysis, don’t hesitate to reach out. Our team is always excited to connect with fellow researchers and practitioners in the GIS and remote sensing community.

We typically respond within 24-48 hours and welcome discussions about methodology, data sources, and potential research partnerships.