knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 8, fig.height = 6, warning = FALSE, message = FALSE )
This vignette demonstrates comprehensive vegetation analysis using GeoSpatialSuite's 60+ vegetation indices. Learn to monitor plant health, detect stress, and analyze agricultural productivity using satellite imagery.
GeoSpatialSuite supports over 60 vegetation indices across multiple categories:
library(geospatialsuite) # See all available vegetation indices all_indices <- list_vegetation_indices() print(all_indices) # View detailed information with formulas detailed_indices <- list_vegetation_indices(detailed = TRUE) head(detailed_indices) # Filter by category basic_indices <- list_vegetation_indices(category = "basic") stress_indices <- list_vegetation_indices(category = "stress")
Basic Vegetation Indices (10): - NDVI, SAVI, MSAVI, OSAVI, EVI, EVI2, DVI, RVI, GNDVI, WDVI
Enhanced/Improved Indices (12): - ARVI, RDVI, PVI, IPVI, TNDVI, GEMI, VARI, TSAVI, ATSAVI, GESAVI, MTVI, CTVI
Red Edge and Advanced Indices (10): - NDRE, MTCI, IRECI, S2REP, PSRI, CRI1, CRI2, ARI1, ARI2, MCARI
Stress and Chlorophyll Indices (12): - PRI, SIPI, CCI, NDNI, CARI, TCARI, MTVI1, MTVI2, TVI, NPCI, RARS, NPQI
All 62 vegetation indices with formulas, ranges, and references:
library(geospatialsuite) detailed <- list_vegetation_indices(detailed = TRUE) knitr::kable( detailed[, c("Index", "Category", "Formula", "Range", "Reference")], caption = "Complete Vegetation Indices Reference - All 62 Indices", format = "html" )
# Get all indices with formulas all_indices <- list_vegetation_indices(detailed = TRUE) View(all_indices) # Filter by category stress <- list_vegetation_indices(category = "stress", detailed = TRUE) water <- list_vegetation_indices(application = "water", detailed = TRUE) # Get specific index information ndvi_info <- all_indices[all_indices$Index == "NDVI", ] cat(sprintf("NDVI Formula: %s\n", ndvi_info$Formula)) cat(sprintf("NDVI Range: %s\n", ndvi_info$Range)) cat(sprintf("Reference: %s\n", ndvi_info$Reference))
The most common vegetation analysis:
# Simple NDVI from individual bands ndvi <- calculate_vegetation_index( red = "landsat_red.tif", nir = "landsat_nir.tif", index_type = "NDVI", verbose = TRUE ) # View results terra::plot(ndvi, main = "NDVI Analysis", col = colorRampPalette(c("brown", "yellow", "green"))(100)) # Check value range summary(terra::values(ndvi, mat = FALSE))
Calculate several indices at once for comprehensive analysis:
# Calculate multiple vegetation indices vegetation_stack <- calculate_multiple_indices( red = red_band, nir = nir_band, blue = blue_band, green = green_band, indices = c("NDVI", "EVI", "SAVI", "GNDVI", "DVI", "RVI"), output_stack = TRUE, region_boundary = "Ohio", verbose = TRUE ) # Access individual indices ndvi_layer <- vegetation_stack$NDVI evi_layer <- vegetation_stack$EVI # Create comparison visualization terra::plot(vegetation_stack, main = names(vegetation_stack))
Work with multi-band satellite imagery:
# From multi-band raster with auto-detection evi <- calculate_vegetation_index( spectral_data = "sentinel2_image.tif", index_type = "EVI", auto_detect_bands = TRUE, verbose = TRUE ) # From directory with band detection savi <- calculate_vegetation_index( spectral_data = "/path/to/sentinel/bands/", index_type = "SAVI", auto_detect_bands = TRUE ) # Custom band names for non-standard data ndvi_custom <- calculate_vegetation_index( spectral_data = custom_satellite_data, band_names = c("B4", "B3", "B2", "B8"), # Red, Green, Blue, NIR index_type = "NDVI" )
Use calculate_ndvi_enhanced() for time series:
# Time series NDVI with quality control ndvi_series <- calculate_ndvi_enhanced( red_data = "/path/to/red/time_series/", nir_data = "/path/to/nir/time_series/", match_by_date = TRUE, # Automatically match files by date quality_filter = TRUE, # Remove outliers temporal_smoothing = TRUE, # Smooth time series clamp_range = c(-0.2, 1), verbose = TRUE ) # Plot time series layers terra::plot(ndvi_series, main = paste("NDVI", names(ndvi_series))) # Calculate temporal statistics temporal_mean <- terra::app(ndvi_series, mean, na.rm = TRUE) temporal_cv <- terra::app(ndvi_series, function(x) sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE))
Analyze vegetation changes over time:
# Temporal analysis workflow temporal_results <- analyze_temporal_changes( data_list = c("ndvi_2020.tif", "ndvi_2021.tif", "ndvi_2022.tif", "ndvi_2023.tif"), dates = c("2020", "2021", "2022", "2023"), region_boundary = "Iowa", analysis_type = "trend", output_folder = "temporal_analysis/" ) # Access trend results slope_raster <- temporal_results$trend_rasters$slope r_squared_raster <- temporal_results$trend_rasters$r_squared # Interpret trends # Positive slope = increasing vegetation # Negative slope = declining vegetation # High R² = strong temporal trend
Analyze vegetation for specific crops:
# Comprehensive crop vegetation analysis corn_analysis <- analyze_crop_vegetation( spectral_data = "sentinel2_data.tif", crop_type = "corn", growth_stage = "mid", analysis_type = "comprehensive", cdl_mask = corn_mask, verbose = TRUE ) # Access results vegetation_indices <- corn_analysis$vegetation_indices stress_analysis <- corn_analysis$analysis_results$stress_analysis yield_analysis <- corn_analysis$analysis_results$yield_analysis # View stress detection results print(stress_analysis$NDVI)
Monitor crop development through the season:
# Early season analysis (emergence to vegetative) early_season <- analyze_crop_vegetation( spectral_data = "may_sentinel2.tif", crop_type = "soybeans", growth_stage = "early", analysis_type = "growth" ) # Mid-season analysis (reproductive stage) mid_season <- analyze_crop_vegetation( spectral_data = "july_sentinel2.tif", crop_type = "soybeans", growth_stage = "mid", analysis_type = "stress" ) # Compare growth stages early_ndvi <- early_season$vegetation_indices$NDVI mid_ndvi <- mid_season$vegetation_indices$NDVI growth_change <- mid_ndvi - early_ndvi
Use specialized indices for stress detection:
# Calculate stress-sensitive indices stress_indices <- calculate_multiple_indices( red = red_band, nir = nir_band, green = green_band, red_edge = red_edge_band, # If available indices = c("NDVI", "PRI", "SIPI", "NDRE"), output_stack = TRUE ) # Stress analysis thresholds ndvi_values <- terra::values(stress_indices$NDVI, mat = FALSE) healthy_threshold <- 0.6 stress_threshold <- 0.4 # Classify stress levels stress_mask <- terra::classify(stress_indices$NDVI, matrix(c(-1, stress_threshold, 1, # Severe stress stress_threshold, healthy_threshold, 2, # Moderate stress healthy_threshold, 1, 3), ncol = 3, byrow = TRUE)) # Visualization stress_colors <- c("red", "orange", "green") terra::plot(stress_mask, main = "Vegetation Stress Classification", col = stress_colors, legend = c("Severe", "Moderate", "Healthy"))
Monitor vegetation water content:
# Water content indices water_stress <- calculate_multiple_indices( nir = nir_band, swir1 = swir1_band, indices = c("NDMI", "MSI", "NDII"), output_stack = TRUE ) # NDMI interpretation: # High NDMI (>0.4) = High water content # Low NDMI (<0.1) = Water stress # MSI interpretation (opposite of NDMI): # Low MSI (<1.0) = High moisture # High MSI (>1.6) = Water stress # Create water stress map water_stress_binary <- terra::classify(water_stress$NDMI, matrix(c(-1, 0.1, 1, # Water stress 0.1, 0.4, 2, # Moderate 0.4, 1, 3), ncol = 3, byrow = TRUE))
Apply quality controls to vegetation data:
# Enhanced NDVI with quality filtering ndvi_quality <- calculate_ndvi_enhanced( red_data = red_raster, nir_data = nir_raster, quality_filter = TRUE, # Remove outliers clamp_range = c(-0.2, 1), # Reasonable NDVI range temporal_smoothing = FALSE, # For single date verbose = TRUE ) # Custom quality filtering vegetation_filtered <- calculate_vegetation_index( red = red_band, nir = nir_band, index_type = "NDVI", mask_invalid = TRUE, # Remove invalid values clamp_range = c(-1, 1) # Theoretical NDVI range )
# Compare with field measurements field_ndvi_validation <- universal_spatial_join( source_data = "field_measurements.csv", # Ground truth points target_data = ndvi_raster, # Satellite NDVI method = "extract", buffer_distance = 30, # 30m buffer around points summary_function = "mean" ) # Calculate correlation observed <- field_ndvi_validation$field_ndvi predicted <- field_ndvi_validation$extracted_NDVI correlation <- cor(observed, predicted, use = "complete.obs") rmse <- sqrt(mean((observed - predicted)^2, na.rm = TRUE)) print(paste("Validation R =", round(correlation, 3))) print(paste("RMSE =", round(rmse, 4)))
Track vegetation phenology over time:
# Create NDVI time series for phenology phenology_analysis <- analyze_temporal_changes( data_list = list.files("/ndvi/2023/", pattern = "*.tif", full.names = TRUE), dates = extract_dates_universal(list.files("/ndvi/2023/", pattern = "*.tif")), region_boundary = "Iowa", analysis_type = "seasonal", output_folder = "phenology_results/" ) # Access seasonal patterns spring_ndvi <- phenology_analysis$seasonal_rasters$Spring summer_ndvi <- phenology_analysis$seasonal_rasters$Summer fall_ndvi <- phenology_analysis$seasonal_rasters$Fall # Calculate growing season metrics peak_season <- terra::app(c(spring_ndvi, summer_ndvi), max, na.rm = TRUE) growing_season_range <- summer_ndvi - spring_ndvi
Combine data from different sensors:
# Landsat NDVI (30m resolution) landsat_ndvi <- calculate_vegetation_index( red = "landsat8_red.tif", nir = "landsat8_nir.tif", index_type = "NDVI" ) # MODIS NDVI (250m resolution) modis_ndvi <- calculate_vegetation_index( red = "modis_red.tif", nir = "modis_nir.tif", index_type = "NDVI" ) # Resample MODIS to Landsat resolution for comparison modis_resampled <- universal_spatial_join( source_data = modis_ndvi, target_data = landsat_ndvi, method = "resample", summary_function = "bilinear" ) # Compare sensors sensor_difference <- landsat_ndvi - modis_resampled correlation <- calculate_spatial_correlation(landsat_ndvi, modis_resampled)
# Forest-specific indices forest_indices <- calculate_multiple_indices( red = red_band, nir = nir_band, swir1 = swir1_band, swir2 = swir2_band, indices = c("NDVI", "EVI", "NBR", "NDMI"), # Normalized Burn Ratio, moisture region_boundary = "forest_boundary.shp", output_stack = TRUE ) # Burn severity assessment using NBR pre_fire_nbr <- forest_indices$NBR # Before fire post_fire_nbr <- calculate_vegetation_index( red = "post_fire_red.tif", nir = "post_fire_nir.tif", swir2 = "post_fire_swir2.tif", index_type = "NBR" ) # Calculate burn severity (dNBR) burn_severity <- pre_fire_nbr - post_fire_nbr # Classify burn severity severity_classes <- terra::classify(burn_severity, matrix(c(-Inf, 0.1, 1, # Unburned 0.1, 0.27, 2, # Low severity 0.27, 0.44, 3, # Moderate-low 0.44, 0.66, 4, # Moderate-high 0.66, Inf, 5), ncol = 3, byrow = TRUE))
# Grassland-specific analysis grassland_health <- calculate_multiple_indices( red = red_band, nir = nir_band, green = green_band, indices = c("NDVI", "SAVI", "MSAVI", "GNDVI"), # Soil-adjusted indices output_stack = TRUE ) # Analyze grazing impact grazed_area_ndvi <- terra::mask(grassland_health$NDVI, grazing_areas) ungrazed_area_ndvi <- terra::mask(grassland_health$NDVI, ungrazed_areas) # Compare means grazed_mean <- terra::global(grazed_area_ndvi, "mean", na.rm = TRUE) ungrazed_mean <- terra::global(ungrazed_area_ndvi, "mean", na.rm = TRUE) grazing_impact <- ungrazed_mean - grazed_mean
# Urban vegetation analysis with atmospheric correction urban_vegetation <- calculate_vegetation_index( red = urban_red, nir = urban_nir, blue = urban_blue, index_type = "ARVI", # Atmospherically Resistant VI region_boundary = "city_boundary.shp" ) # Green space analysis green_space_threshold <- 0.3 urban_greenness <- terra::classify(urban_vegetation, matrix(c(-1, green_space_threshold, 0, # Non-vegetated green_space_threshold, 1, 1), ncol = 3, byrow = TRUE)) # Vegetated # Calculate green space statistics total_urban_area <- terra::ncell(urban_greenness) * prod(terra::res(urban_greenness)) green_area <- terra::global(urban_greenness, "sum", na.rm = TRUE) * prod(terra::res(urban_greenness)) green_space_percentage <- (green_area / total_urban_area) * 100
NDVI (Normalized Difference Vegetation Index): - Best for: General vegetation monitoring, biomass estimation - Range: -1 to 1 (vegetation typically 0.3-0.9) - Limitations: Saturates in dense vegetation
ndvi <- calculate_vegetation_index(red = red, nir = nir, index_type = "NDVI")
EVI (Enhanced Vegetation Index): - Best for: Dense vegetation, reducing atmospheric effects - Range: -1 to 3 (vegetation typically 0.2-1.0) - Advantages: Less saturation than NDVI
evi <- calculate_vegetation_index(red = red, nir = nir, blue = blue, index_type = "EVI")
SAVI (Soil Adjusted Vegetation Index): - Best for: Areas with exposed soil, early season crops - Range: -1 to 1.5 - Advantages: Reduces soil background effects
savi <- calculate_vegetation_index(red = red, nir = nir, index_type = "SAVI")
PRI (Photochemical Reflectance Index): - Best for: Plant stress detection, photosynthetic efficiency - Range: -1 to 1 - Applications: Early stress detection before visible symptoms
# Note: PRI typically uses 531nm and 570nm bands # Using green and NIR as approximation pri <- calculate_vegetation_index(green = green, nir = nir, index_type = "PRI")
Crop Health Monitoring:
crop_health <- calculate_multiple_indices( red = red, nir = nir, green = green, indices = c("NDVI", "GNDVI", "SIPI"), # Structure Insensitive Pigment Index output_stack = TRUE )
Drought Monitoring:
drought_indices <- calculate_multiple_indices( nir = nir, swir1 = swir1, indices = c("NDMI", "MSI"), # Moisture indices output_stack = TRUE )
Precision Agriculture:
precision_ag <- calculate_multiple_indices( red = red, nir = nir, green = green, red_edge = red_edge, indices = c("NDVI", "NDRE", "GNDVI", "CCI"), # Chlorophyll indices output_stack = TRUE )
# NDVI-specific visualization create_spatial_map( spatial_data = ndvi_raster, color_scheme = "ndvi", # NDVI-specific colors region_boundary = "study_area.shp", title = "NDVI Analysis - Growing Season Peak", output_file = "ndvi_analysis.png" ) # Multi-index comparison create_comparison_map( data1 = ndvi_raster, data2 = evi_raster, comparison_type = "side_by_side", titles = c("NDVI", "EVI"), color_scheme = "viridis" )
# Interactive vegetation analysis interactive_veg_map <- create_interactive_map( spatial_data = vegetation_points, fill_variable = "NDVI", popup_vars = c("NDVI", "EVI", "collection_date"), basemap = "satellite", title = "Interactive Vegetation Analysis" ) # Save interactive map htmlwidgets::saveWidget(interactive_veg_map, "vegetation_interactive.html")
# Calculate comprehensive statistics vegetation_stats <- terra::global(vegetation_stack, c("mean", "sd", "min", "max"), na.rm = TRUE) print(vegetation_stats) # Zonal statistics by land cover landcover_stats <- universal_spatial_join( source_data = vegetation_stack, target_data = "landcover_polygons.shp", method = "zonal", summary_function = "mean" ) # Statistics by vegetation class healthy_vegetation <- vegetation_stack$NDVI > 0.6 moderate_vegetation <- vegetation_stack$NDVI > 0.3 & vegetation_stack$NDVI <= 0.6 poor_vegetation <- vegetation_stack$NDVI <= 0.3 # Calculate percentages total_pixels <- terra::ncell(vegetation_stack$NDVI) healthy_pct <- terra::global(healthy_vegetation, "sum", na.rm = TRUE) / total_pixels * 100 moderate_pct <- terra::global(moderate_vegetation, "sum", na.rm = TRUE) / total_pixels * 100 poor_pct <- terra::global(poor_vegetation, "sum", na.rm = TRUE) / total_pixels * 100
# Analyze relationships between indices index_correlations <- analyze_variable_correlations( variable_list = list( NDVI = vegetation_stack$NDVI, EVI = vegetation_stack$EVI, SAVI = vegetation_stack$SAVI, GNDVI = vegetation_stack$GNDVI ), method = "pearson", create_plots = TRUE, output_folder = "correlation_analysis/" ) # View correlation matrix print(index_correlations$correlation_matrix)
Complete workflow for monitoring corn fields:
# 1. Define study area study_area <- get_region_boundary("Iowa:Story") # Story County, Iowa # 2. Create corn mask corn_mask <- create_crop_mask( cdl_data = "cdl_iowa_2023.tif", crop_codes = get_comprehensive_cdl_codes("corn"), region_boundary = study_area ) # 3. Calculate vegetation indices for corn areas corn_vegetation <- calculate_multiple_indices( spectral_data = "sentinel2_iowa_july.tif", indices = c("NDVI", "EVI", "GNDVI", "SAVI"), auto_detect_bands = TRUE, output_stack = TRUE ) # 4. Apply corn mask corn_vegetation_masked <- terra::mask(corn_vegetation, corn_mask) # 5. Analyze corn health corn_stats <- terra::global(corn_vegetation_masked, c("mean", "sd", "min", "max"), na.rm = TRUE) # 6. Create visualization quick_map(corn_vegetation_masked$NDVI, title = "Story County Corn NDVI - July 2023") # 7. Save results terra::writeRaster(corn_vegetation_masked, "story_county_corn_indices.tif")
Detect and map vegetation stress:
# 1. Load multi-temporal data april_data <- calculate_ndvi_enhanced("april_sentinel2.tif") july_data <- calculate_ndvi_enhanced("july_sentinel2.tif") # 2. Calculate stress indices stress_indices <- calculate_multiple_indices( spectral_data = "july_sentinel2.tif", indices = c("NDVI", "PRI", "SIPI", "NDMI"), auto_detect_bands = TRUE, output_stack = TRUE ) # 3. Identify stressed areas # NDVI decline from April to July ndvi_change <- july_data - april_data stress_areas <- ndvi_change < -0.2 # Significant decline # Water stress (low NDMI) water_stress <- stress_indices$NDMI < 0.2 # Combined stress map combined_stress <- stress_areas | water_stress # 4. Quantify stress total_area <- terra::ncell(combined_stress) * prod(terra::res(combined_stress)) / 10000 # hectares stressed_pixels <- terra::global(combined_stress, "sum", na.rm = TRUE) stressed_area <- stressed_pixels * prod(terra::res(combined_stress)) / 10000 # hectares stress_percentage <- (stressed_area / total_area) * 100 print(paste("Stressed area:", round(stressed_area, 1), "hectares")) print(paste("Stress percentage:", round(stress_percentage, 1), "%"))
Large-scale vegetation analysis:
# 1. Multi-state analysis great_lakes_states <- c("Michigan", "Ohio", "Indiana", "Illinois", "Wisconsin") regional_ndvi <- list() for (state in great_lakes_states) { state_ndvi <- calculate_vegetation_index( spectral_data = paste0("/data/", tolower(state), "_modis.tif"), index_type = "NDVI", region_boundary = state, auto_detect_bands = TRUE ) regional_ndvi[[state]] <- state_ndvi } # 2. Create regional mosaic great_lakes_mosaic <- create_raster_mosaic( input_data = regional_ndvi, method = "merge", region_boundary = "Great Lakes Region" ) # 3. Regional statistics regional_stats <- terra::global(great_lakes_mosaic, c("mean", "sd", "min", "max"), na.rm = TRUE) # 4. State-by-state comparison state_means <- sapply(regional_ndvi, function(x) { terra::global(x, "mean", na.rm = TRUE)[[1]] }) names(state_means) <- great_lakes_states print(sort(state_means, decreasing = TRUE))
# If auto-detection fails, specify band names manually custom_ndvi <- calculate_vegetation_index( spectral_data = "unusual_satellite_data.tif", band_names = c("Band_4_Red", "Band_5_NIR"), # Custom names index_type = "NDVI", auto_detect_bands = FALSE ) # Or use individual band approach manual_ndvi <- calculate_vegetation_index( red = satellite_data$Band_4_Red, nir = satellite_data$Band_5_NIR, index_type = "NDVI" )
# Automatic CRS fixing robust_indices <- calculate_multiple_indices( red = "red_utm.tif", # UTM projection nir = "nir_geographic.tif", # Geographic coordinates indices = c("NDVI", "EVI"), auto_crs_fix = TRUE, # Automatically handle CRS differences verbose = TRUE # See what's being fixed )
# Process large areas efficiently # 1. Use chunked processing large_area_ndvi <- calculate_vegetation_index( spectral_data = "very_large_raster.tif", index_type = "NDVI", chunk_size = 500000, # Smaller chunks auto_detect_bands = TRUE ) # 2. Process by regions us_states <- c("Ohio", "Michigan", "Indiana") state_results <- list() for (state in us_states) { state_results[[state]] <- calculate_vegetation_index( spectral_data = "continental_satellite_data.tif", index_type = "NDVI", region_boundary = state, # Process each state separately auto_detect_bands = TRUE ) } # 3. Combine results combined_results <- create_raster_mosaic(state_results, method = "merge")
# Efficient workflow optimized_workflow <- function(satellite_data, study_region) { # 1. Crop to region first (reduces data size) cropped_data <- universal_spatial_join( source_data = satellite_data, target_data = get_region_boundary(study_region), method = "crop" ) # 2. Calculate multiple indices in one call vegetation_indices <- calculate_multiple_indices( spectral_data = cropped_data, indices = c("NDVI", "EVI", "SAVI", "GNDVI"), auto_detect_bands = TRUE, output_stack = TRUE, parallel = FALSE # Use FALSE for stability ) # 3. Cache results terra::writeRaster(vegetation_indices, "cached_vegetation_indices.tif") return(vegetation_indices) }
Cropland:
- 0.2-0.4: Bare soil/early growth
- 0.4-0.6: Developing vegetation
- 0.6-0.8: Healthy, mature crops
- 0.8-0.9: Peak vegetation (dense crops)
Forest: - 0.4-0.6: Sparse forest/stressed trees - 0.6-0.8: Healthy forest - 0.8-0.9: Dense, healthy forest
Grassland: - 0.2-0.4: Sparse grass/dormant season - 0.4-0.6: Moderate grass cover - 0.6-0.8: Dense, healthy grassland
Temperate Crops (Corn/Soybeans): - April-May: 0.2-0.4 (emergence) - June-July: 0.4-0.7 (vegetative growth) - July-August: 0.6-0.9 (peak season) - September-October: 0.4-0.6 (senescence)
Create your own vegetation indices:
# Custom ratio index custom_ratio <- nir_band / red_band names(custom_ratio) <- "Custom_Ratio" # Custom normalized difference custom_nd <- (nir_band - green_band) / (nir_band + green_band) names(custom_nd) <- "Custom_ND" # Combine with standard indices all_indices <- c( calculate_vegetation_index(red = red, nir = nir, index_type = "NDVI"), custom_ratio, custom_nd )
# Combine vegetation indices with environmental data environmental_analysis <- universal_spatial_join( source_data = vegetation_indices, target_data = list( precipitation = "annual_precip.tif", temperature = "mean_temp.tif", elevation = "dem.tif" ), method = "extract" ) # Analyze relationships vegetation_env_correlation <- analyze_variable_correlations( variable_list = list( NDVI = vegetation_indices$NDVI, precipitation = environmental_data$precipitation, temperature = environmental_data$temperature ), create_plots = TRUE )
This vignette covered comprehensive vegetation analysis with GeoSpatialSuite:
# Explore more indices list_vegetation_indices(detailed = TRUE) # Get help ?calculate_vegetation_index ?calculate_multiple_indices ?analyze_crop_vegetation # Test your installation test_geospatialsuite_package_simple(verbose = TRUE)
The calculate_vegetation_index() and analyze_crop_vegetation() functions support automatic band detection from multiple satellite platforms. This document explains how band naming works and what formats are supported.
All band names are case-insensitive.
✅ These are all equivalent:
- "red", "RED", "Red", "rEd"
- "nir", "NIR", "Nir", "nIR"
- "B4", "b4", "B04", "b04"
Recognized patterns:
- Generic: red, RED, Red
- Landsat 8/9: B4, b4, band4, Band_4, Band4
- Sentinel-2: B04, b04
- Generic numbered: band_4, sr_band4
Example:
# All of these will be recognized as the red band calculate_vegetation_index(red = red_band, nir = nir_band, index_type = "NDVI") calculate_vegetation_index(spectral_data = raster_with_band_named_"B4", auto_detect_bands = TRUE) calculate_vegetation_index(spectral_data = raster_with_band_named_"RED", auto_detect_bands = TRUE)
Recognized patterns:
- Generic: nir, NIR, Nir, near_infrared
- Landsat 8/9: B5, b5, band5, Band_5, Band5
- Sentinel-2: B08, b08, B8, b8, band8, Band_8, Band8
Recognized patterns:
- Generic: blue, BLUE, Blue
- Landsat 8/9: B2, b2, band2, Band_2, Band2
- Sentinel-2: B02, b02
Recognized patterns:
- Generic: green, GREEN, Green
- Landsat 8/9: B3, b3, band3, Band_3, Band3
- Sentinel-2: B03, b03
Recognized patterns:
- Generic: swir1, SWIR1, Swir1, shortwave_infrared_1, SWIR_1
- Landsat 8/9: B6, b6
- Sentinel-2: B11, b11
Recognized patterns:
- Generic: swir2, SWIR2, Swir2, shortwave_infrared_2, SWIR_2
- Landsat 8/9: B7, b7
- Sentinel-2: B12, b12
Recognized patterns:
- Generic: rededge, RedEdge, red_edge, RED_EDGE, RE, re
- Sentinel-2: B05, b05, B5, b5 (note: conflicts with Landsat NIR)
Recognized patterns:
- Generic: coastal, COASTAL, Coastal, aerosol
- Landsat 8/9: B1, b1
- Sentinel-2: B01, b01
Band Mapping:
| Band | Number | Wavelength | geospatialsuite Name |
|------|--------|------------|---------------------|
| Coastal/Aerosol | B1 | 0.43-0.45 µm | coastal, B1 |
| Blue | B2 | 0.45-0.51 µm | blue, B2 |
| Green | B3 | 0.53-0.59 µm | green, B3 |
| Red | B4 | 0.64-0.67 µm | red, B4 |
| NIR | B5 | 0.85-0.88 µm | nir, B5 |
| SWIR1 | B6 | 1.57-1.65 µm | swir1, B6 |
| SWIR2 | B7 | 2.11-2.29 µm | swir2, B7 |
Example - Landsat Scene:
library(geospatialsuite) library(terra) # If your Landsat file has band names: B1, B2, B3, B4, B5, B6, B7 landsat <- rast("LC08_L2SP_029030_20230615_B1-7.tif") # Auto-detection will work automatically ndvi <- calculate_vegetation_index( spectral_data = landsat, index_type = "NDVI", auto_detect_bands = TRUE ) # Or specify bands explicitly ndvi <- calculate_vegetation_index( red = landsat[["B4"]], nir = landsat[["B5"]], index_type = "NDVI" )
Band Mapping:
| Band | Number | Wavelength | Resolution | geospatialsuite Name |
|------|--------|------------|-----------|---------------------|
| Coastal | B01 | 0.433-0.453 µm | 60m | coastal, B01, B1 |
| Blue | B02 | 0.458-0.523 µm | 10m | blue, B02, B2 |
| Green | B03 | 0.543-0.578 µm | 10m | green, B03, B3 |
| Red | B04 | 0.650-0.680 µm | 10m | red, B04, B4 |
| Red Edge 1 | B05 | 0.698-0.713 µm | 20m | red_edge, B05, B5 |
| Red Edge 2 | B06 | 0.733-0.748 µm | 20m | N/A |
| Red Edge 3 | B07 | 0.765-0.785 µm | 20m | N/A |
| NIR | B08 | 0.785-0.900 µm | 10m | nir, B08, B8 |
| SWIR1 | B11 | 1.565-1.655 µm | 20m | swir1, B11 |
| SWIR2 | B12 | 2.100-2.280 µm | 20m | swir2, B12 |
Example - Sentinel-2 Scene:
# If your Sentinel-2 file has standard band names sentinel <- rast("S2A_MSIL2A_20230615_B02-B12.tif") # Auto-detection works ndvi <- calculate_vegetation_index( spectral_data = sentinel, index_type = "NDVI", auto_detect_bands = TRUE ) # Calculate red edge index (requires Sentinel-2) ndre <- calculate_vegetation_index( spectral_data = sentinel, index_type = "NDRE", auto_detect_bands = TRUE ) # Or use specific band names evi <- calculate_vegetation_index( red = sentinel[["B04"]], nir = sentinel[["B08"]], blue = sentinel[["B02"]], index_type = "EVI" )
Band Mapping:
| Band | Number | Wavelength | geospatialsuite Name |
|------|--------|------------|---------------------|
| Red | 1 | 0.620-0.670 µm | red, band1 |
| NIR | 2 | 0.841-0.876 µm | nir, band2 |
| Blue | 3 | 0.459-0.479 µm | blue, band3 |
| Green | 4 | 0.545-0.565 µm | green, band4 |
| SWIR1 | 6 | 1.628-1.652 µm | swir1, band6 |
| SWIR2 | 7 | 2.105-2.155 µm | swir2, band7 |
Example - MODIS:
modis <- rast("MOD13Q1_NDVI_2023165.tif") # If bands are named generically ndvi <- calculate_vegetation_index( spectral_data = modis, index_type = "NDVI", auto_detect_bands = TRUE )
If your raster has non-standard band names, you have three options:
# Your raster has bands: "band_A", "band_B", "band_C", "band_D" my_raster <- rast("custom_satellite.tif") # Rename to standard names names(my_raster) <- c("red", "green", "blue", "nir") # Now auto-detection works ndvi <- calculate_vegetation_index( spectral_data = my_raster, index_type = "NDVI", auto_detect_bands = TRUE )
# Tell the function which bands are which ndvi <- calculate_vegetation_index( spectral_data = my_raster, band_names = c("band_A", "band_B", "band_C", "band_D"), # Order: Red, Green, Blue, NIR index_type = "NDVI", auto_detect_bands = FALSE )
# Extract specific layers red_band <- my_raster[[1]] # Assuming layer 1 is red nir_band <- my_raster[[4]] # Assuming layer 4 is NIR # Pass explicitly ndvi <- calculate_vegetation_index( red = red_band, nir = nir_band, index_type = "NDVI" )
Problem: Your band names don't match any recognized patterns
Solution:
# Check your band names names(my_raster) # [1] "sr_band_4" "sr_band_5" "sr_band_3" "sr_band_2" # Rename them names(my_raster) <- c("red", "nir", "green", "blue") # Or extract and pass explicitly calculate_vegetation_index( red = my_raster[[1]], nir = my_raster[[2]], index_type = "NDVI" )
Problem: Auto-detection picked the wrong bands (e.g., Sentinel-2 B05 detected as NIR instead of Red Edge)
Solution:
# Don't use auto-detection, be explicit calculate_vegetation_index( red = my_raster[["B04"]], nir = my_raster[["B08"]], red_edge = my_raster[["B05"]], index_type = "NDRE" )
Problem: Your bands are in separate files
Solution A - List of files:
# Create file list band_files <- c( red = "LC08_B4.tif", nir = "LC08_B5.tif", blue = "LC08_B2.tif" ) # Let the function load them evi <- calculate_vegetation_index( spectral_data = band_files, index_type = "EVI" )
Solution B - Directory:
# If all bands are in one directory with recognizable names ndvi <- calculate_vegetation_index( spectral_data = "/path/to/landsat/bands/", index_type = "NDVI", auto_detect_bands = TRUE )
When using auto-detection, the function searches in this order:
"red" matches "red", "RED", "Red"
Satellite-specific patterns
"B4" for Landsat red"B04" for Sentinel-2 red
Generic patterns
"band4", "Band_4", etc.
Partial matches (regex, case-insensitive)
Any layer with "red" in the name
Position-based fallback (if all else fails)
To see which bands were detected:
# Turn on verbose mode result <- calculate_vegetation_index( spectral_data = my_raster, index_type = "NDVI", auto_detect_bands = TRUE, verbose = TRUE # This will print which bands were detected )
Expected output:
Processing spectral_data input... Extracting bands from multi-band raster... Exact match detected red band: B4 Exact match detected nir band: B8 Starting NDVI calculation with comprehensive input handling...
red, nir, blue, etc.)| Your Data | Band Detection Method | Example |
|-----------|----------------------|---------|
| Landsat with standard names | Auto-detect | auto_detect_bands = TRUE |
| Sentinel-2 with standard names | Auto-detect | auto_detect_bands = TRUE |
| Generic names (red, nir, blue) | Auto-detect | auto_detect_bands = TRUE |
| Custom names | Rename first | names(raster) <- c("red", "nir", ...) |
| Separate files | Pass list | spectral_data = c(red="file1.tif", ...) |
| Non-standard structure | Extract & pass | red = raster[[1]], nir = raster[[4]] |
This work was developed by the GeoSpatialSuite team with contributions from: Olatunde D. Akanbi, Erika I. Barcelos, and Roger H. French.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.