ndvi2gif - Comprehensive Example Notebook#
Overview This notebook demonstrates the key functionalities of ndvi2gif v0.3.0, including the new SAR indices, flexible percentiles, and robust handling of incomplete years. Installation
Installation#
pip install ndvi2gif
1. Basic Setup#
import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="geemap.conversion")
import ee
import geemap
import matplotlib.pyplot as plt
from ndvi2gif import NdviSeasonality, TimeSeriesAnalyzer, SpatialTrendAnalyzer, LandCoverClassifier
#from ndvi2gif import NdviSeasonality
#from ndvi2gif import NdviSeasonality
#from ndvi2gif.timeseries import TimeSeriesAnalyzer, SpatialTrendAnalyzer
print("✓ Módulos importados correctamente")
# Authenticate & initialize Earth Engine
# ee.Authenticate() # Just first time
ee.Initialize()
# Create interactive map
Map = geemap.Map()
Map
✓ Módulos importados correctamente
2. Region of Interest (ROI) Options#
2.1 Draw ROI on Map#
Draw a polygon on the map above, then:#
# Draw a roi on the map wherever you want
roi = Map.draw_last_feature
Please note that Landsat path-row, Senmtinel 2 tiles and eLTER sites deimsID are also available as rois
2.2 Use Shapefile/GeoJSON | Landsat Path/Row | Sentinel-2 MGRS Tile | eLTER DEIMS Site ID are also available Rois#
roi_shapefile = 'path/to/your/shapefile.shp'
roi_geojson = 'path/to/your/file.geojson'
roi_coords = ee.Geometry.Rectangle([-6.766, 36.776, -5.867, 37.202]) # [xMin, yMin, xMax, yMax]
roi_landsat = 'wrs:202,034' # Format: 'wrs:path,row'
# Example: Path 202, Row 034 covers parts of Spain
roi_s2_tile = 's2:T30TYN' # Format: 's2:TILE_ID'
# Example: T30TYN covers parts of Central Spain
# Use European Long-Term Ecosystem Research sites
roi_elter = 'deimsid:ab8278e6-0b71-4b36-a6d2-e8f34aa3df30' # Format: 'deimsid:SITE_ID'
# Note: it could required 'deims' package: pip install deims in case conda couldn't make the installation
3. Optical Satellites (Sentinel-2, Landsat, MODIS)#
3.1 Basic NDVI Analysis#
Seasonal NDVI with Sentinel-2#
s2_ndvi = NdviSeasonality(
roi=roi_coords,
periods=4, # Seasonal (winter, spring, summer, autumn)
start_year=2018,
end_year=2024, # Note: end_year is exclusive
sat='S2', # Sentinel-2
key='max', # median could also be percentile 50 but its also included as median
index='ndvi' # NDVI index
)
# Generate composites
s2_composite = s2_ndvi.get_year_composite()
print("S2 NDVI bands:", s2_composite.first().bandNames().getInfo())
# Visualize on map
vizParams_ndvi = {'bands': ['winter', 'spring', 'summer'], 'min': 0.2, 'max': 0.8}
Map.addLayer(s2_composite.median(), vizParams_ndvi, 'S2 NDVI Seasonal') # In this line we are running all the 4 season bands and making "on the fly" the max median of all years
There we go again...
Using MODIS Terra + Aqua LST (maximum coverage)
Using all Sentinel-1 orbits (ascending + descending).
Applying S1 ARD preprocessing:
- Speckle filter: REFINED_LEE
- Terrain correction: True
- Terrain model: VOLUME
Year 2018: Successfully processed 4 periods using ndvi index
Year 2019: Successfully processed 4 periods using ndvi index
Year 2020: Successfully processed 4 periods using ndvi index
Year 2021: Successfully processed 4 periods using ndvi index
Year 2022: Successfully processed 4 periods using ndvi index
Year 2023: Successfully processed 4 periods using ndvi index
S2 NDVI bands: ['winter', 'spring', 'summer', 'autumn']
# Create a new interactive map, otherwise you will be always working in the first map
# It could be useful add numbers to be sure in which map are you working on
Map1 = geemap.Map()
Map1
roi_drawn = Map1.draw_last_feature # Your drawn geometry
3.2 Monthly Analysis with Different Indices and using flexible percentile#
Monthly EVI analysis#
s2_evi = NdviSeasonality(
roi=roi_drawn,
periods=12,
start_year=2023,
end_year=2024,
sat='S2',
key='percentile', # When key is percentile then you have to add percentile as parameter to choose the value
percentile=85,
index='savi'
)
s2_evi_composite = s2_evi.get_year_composite()
vizParams_evi = {'bands': ['january', 'june', 'december'], 'min': 0, 'max': 1}
Map1.addLayer(s2_evi_composite.first(), vizParams_evi, 'S2 SAVI Monthly')
There we go again...
Using MODIS Terra + Aqua LST (maximum coverage)
Using all Sentinel-1 orbits (ascending + descending).
Applying S1 ARD preprocessing:
- Speckle filter: REFINED_LEE
- Terrain correction: True
- Terrain model: VOLUME
Year 2023: Successfully processed 12 periods using savi index
Note that you can do something like gets maximun (or any other stats) value at pixel level per band and get something like max value of p85 for may#
Map2 = geemap.Map()
spring = s2_evi_composite.select('may')
spring_max = spring.max()
vis_params = {'bands': ['may'], 'min': 0.22, 'max': 0.55}
Map2.addLayer(spring_max, vis_params, 'spring_max')
Map2.centerObject(roi_drawn, zoom=11)
Map2
4. SAR Analysis with Sentinel-1 (NEW in v0.3.0)#
4.1 Radar Vegetation Index (RVI) - Best for Crop Monitoring#
RVI is more robust than individual polarizations#
roi_deims = 'deimsid:https://deims.org/45722713-80e3-4387-a47b-82c97a6ef62b' # Baixo Sabor Portugal
s1_rvi = NdviSeasonality(
roi=roi_deims,
periods=12, # Monthly for detailed temporal analysis
start_year=2023,
end_year=2024,
sat='S1', # Sentinel-1 SAR
key='median', # Mean composite
index='rvi' # Radar Vegetation Index (NEW)
)
s1_rvi_composite = s1_rvi.get_year_composite()
print("S1 RVI bands:", s1_rvi_composite.first().bandNames().getInfo())
# SAR visualization (different range than optical)
sar_vizParams_rvi = {'bands': ['january', 'june', 'december'], 'min': 2.3, 'max': 2.7}
Map3 = geemap.Map()
Map3.addLayer(s1_rvi_composite.first(), sar_vizParams_rvi, 'S1 RVI Monthly Median')
Map3.centerObject(s1_rvi_composite, zoom=10)
Map3
There we go again...
Con Deims hemos topado, amigo Sancho...
Using MODIS Terra + Aqua LST (maximum coverage)
Using all Sentinel-1 orbits (ascending + descending).
Applying S1 ARD preprocessing:
- Speckle filter: REFINED_LEE
- Terrain correction: True
- Terrain model: VOLUME
Year 2023: Successfully processed 12 periods using rvi index
S1 RVI bands: ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
4.2 VV/VH Ratio - Sensitive to Structural Changes#
VV/VH ratio is excellent for detecting harvest/mowing events#
Map4 = geemap.Map()
s1_ratio = NdviSeasonality(
roi=roi_drawn,
periods=24, # Bi-monthly for high temporal resolution
start_year=2023,
end_year=2024,
sat='S1',
key='median',
index='vv_vh_ratio' # VV/VH polarization ratio (NEW)
)
s1_ratio_composite = s1_ratio.get_year_composite()
sar_vizParams_ratio = {'bands': ['p1', 'p12', 'p24'], 'min': 0.5, 'max': 0.7}
Map4.addLayer(s1_ratio_composite.first(), sar_vizParams_ratio, 'S1 VV/VH Ratio')
Map4.centerObject(roi_drawn, zoom=10)
Map4
There we go again...
Using MODIS Terra + Aqua LST (maximum coverage)
Using all Sentinel-1 orbits (ascending + descending).
Applying S1 ARD preprocessing:
- Speckle filter: REFINED_LEE
- Terrain correction: True
- Terrain model: VOLUME
Year 2023: Successfully processed 24 periods using vv_vh_ratio index
4.3 Advanced SAR Processing with ARD Pipeline#
Mountain area#
Map5 = geemap.Map()
Map5
roi_drawn = Map5.draw_last_feature
s1_ard = NdviSeasonality(
roi=roi_drawn,
periods=24,
start_year=2023,
end_year=2024,
sat='S1',
key='median',
index='rvi',
# ARD preprocessing options
use_sar_ard=True,
sar_speckle_filter='REFINED_LEE',
sar_terrain_correction=True,
sar_terrain_model='VOLUME',
orbit='DESCENDING'
)
s1_ard_ratio_composite = s1_ard.get_year_composite()
vis_params = {'bands': ['p1', 'p12', 'p24'], 'gamma': 1.0, 'min': 2.53, 'max': 2.65}
Map5.addLayer(s1_ard_ratio_composite.max(), vis_params, 'S1 RVI')
There we go again...
Using MODIS Terra + Aqua LST (maximum coverage)
Using Sentinel-1 descending orbits only.
Applying S1 ARD preprocessing:
- Speckle filter: REFINED_LEE
- Terrain correction: True
- Terrain model: VOLUME
Year 2023: Successfully processed 24 periods using rvi index
5. Handling Incomplete Years#
5.1 Current Year Analysis#
Automatically handles incomplete data for current year#
current_year = NdviSeasonality(
roi=roi,
periods=12, # Requests 12 periods
start_year=2025,
end_year=2026, # Remember: end_year is exclusive
sat='S1',
key='mean',
index='rvi'
)
# Will automatically detect and process only available periods
current_composite = current_year.get_year_composite()
print("Available periods for 2025:", current_composite.first().bandNames().getInfo())
# Example output: ['january', 'february', 'march', 'april', 'may', 'june', 'july']
There we go again...
Using MODIS Terra + Aqua LST (maximum coverage)
Using all Sentinel-1 orbits (ascending + descending).
Applying S1 ARD preprocessing:
- Speckle filter: REFINED_LEE
- Terrain correction: True
- Terrain model: VOLUME
No data for period 9 in year 2025
Year 2025: Successfully processed 8 periods using rvi index
Available periods for 2025: ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august']