Extracting Pixel Values from Regional GOES-16 Data (NDVI)

Max NDVI between Jan 1 2022 and Jan 15 2022 and the value of four pixels being displayed

We received the following question:

I attended the “Agrometerology Training Course and NDVI Composites” organized by AEMET and taught by your team at the end of last year. I would like to extract the maximum NDVI value from specific coordinates.

The example Python code below demonstrates how to calculate the maximum NDVI between two dates and also how to extract the max NDVI for coordinates delected by users.

You may select the desired points on the “coordinates” list on line 148. Inside each parenthesis, you put a custom name for that point (eg.: “P1”, “P2”, “P3”, etc), and the lat lon pair.

#-----------------------------------------------------------------------------------------------------------
# Agrometeorology Course - Demonstration: Normalized Difference Vegetation Index (NDVI) - Extract Values
# Author: Diego Souza
#-----------------------------------------------------------------------------------------------------------
# Required modules
from netCDF4 import Dataset # Read / Write NetCDF4 files
import matplotlib.pyplot as plt # Plotting library
from datetime import datetime # Basic Dates and time types
from datetime import timedelta, date, datetime # Basic Dates and time types
import cartopy, cartopy.crs as ccrs # Plot maps
import cartopy.io.shapereader as shpreader # Import shapefiles
import cartopy.feature as cfeature # Cartopy features
import numpy as np # Scientific computing with Python
import os # Miscellaneous operating system interfaces
from utilities import download_CMI # Our own utilities
from utilities import geo2grid, latlon2xy, convertExtent2GOESProjection # Our own utilities
#-----------------------------------------------------------------------------------------------------------
# Input and output directories
input = "/content/Samples"; os.makedirs(input, exist_ok=True)
output = "/content/Animation"; os.makedirs(output, exist_ok=True)
# Desired extent
extent = [-93.0, -60.00, -25.00, 15.00] # Min lon, Min lat, Max lon, Max lat
# Initial date and time to process
date_ini = '202201011800'
# Number of days to accumulate
ndays = 15
# Convert to datetime
date_ini = datetime(int(date_ini[0:4]), int(date_ini[4:6]), int(date_ini[6:8]), int(date_ini[8:10]), int(date_ini[10:12]))
date_end = date_ini + timedelta(days=ndays)
# Create our references for the loop
date_loop = date_ini
#-----------------------------------------------------------------------------------------------------------
# NDVI colormap creation
import matplotlib
colors = ["#8f2723", "#8f2723", "#8f2723", "#8f2723", "#af201b", "#af201b", "#af201b", "#af201b", "#ce4a2e", "#ce4a2e", "#ce4a2e", "#ce4a2e",
"#df744a", "#df744a", "#df744a", "#df744a", "#f0a875", "#f0a875", "#f0a875", "#f0a875", "#fad398", "#fad398", "#fad398", "#fad398",
"#fff8ba",
"#d8eda0", "#d8eda0", "#d8eda0", "#d8eda0", "#bddd8a", "#bddd8a", "#bddd8a", "#bddd8a", "#93c669", "#93c669", "#93c669", "#93c669",
"#5da73e", "#5da73e", "#5da73e", "#5da73e", "#3c9427", "#3c9427", "#3c9427", "#3c9427", "#235117", "#235117", "#235117", "#235117"]
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", colors)
cmap.set_over('#235117')
cmap.set_under('#8f2723')
vmin = 0.1
vmax = 0.8
#-----------------------------------------------------------------------------------------------------------
# Variable to check if is the first iteration
first = True
# Create our references for the loop
date_loop = date_ini
while (date_loop <= date_end):
# Date structure
yyyymmddhhmn = date_loop.strftime('%Y%m%d%H%M')
print(yyyymmddhhmn)
# Download the file for Band 02
file_name_ch02 = download_CMI(yyyymmddhhmn, '2', input)
# Download the file for Band 03
file_name_ch03 = download_CMI(yyyymmddhhmn, '3', input)
#-----------------------------------------------------------------------------------------------------------
# If one the files hasn't been downloaded for some reason, skip the current iteration
if not (os.path.exists(f'{input}/{file_name_ch02}.nc')) or not (os.path.exists(f'{input}/{file_name_ch03}.nc')):
# Increment the date_loop
date_loop = date_loop + timedelta(days=1)
print("The file is not available, skipping this iteration.")
continue
# Open the GOES-R image (Band 02)
file = Dataset(f'{input}/{file_name_ch02}.nc')
# Convert lat/lon to grid-coordinates
lly, llx = geo2grid(extent[1], extent[0], file)
ury, urx = geo2grid(extent[3], extent[2], file)
# Get the pixel values
data_ch02 = file.variables['CMI'][ury:lly, llx:urx][::2 ,::2]
#-----------------------------------------------------------------------------------------------------------
# Open the GOES-R image (Band 03)
file = Dataset(f'{input}/{file_name_ch03}.nc')
# Convert lat/lon to grid-coordinates
lly, llx = geo2grid(extent[1], extent[0], file)
ury, urx = geo2grid(extent[3], extent[2], file)
# Get the pixel values
data_ch03 = file.variables['CMI'][ury:lly, llx:urx]
#-----------------------------------------------------------------------------------------------------------
# Make the arrays equal size
cordX = np.shape(data_ch02)[0], np.shape(data_ch03)[0]
cordY = np.shape(data_ch02)[1], np.shape(data_ch03)[1]
minvalX = np.array(cordX).min()
minvalY = np.array(cordY).min()
data_ch02 = data_ch02[0:minvalX, 0:minvalY]
data_ch03 = data_ch03[0:minvalX, 0:minvalY]
#-----------------------------------------------------------------------------------------------------------
# Calculate the NDVI
data = (data_ch03 - data_ch02) / (data_ch03 + data_ch02)
#-----------------------------------------------------------------------------------------------------------
# Compute data-extent in GOES projection-coordinates
img_extent = convertExtent2GOESProjection(extent)
#-----------------------------------------------------------------------------------------------------------
# If it's the first iteration, create the array that will store the max values
if (first == True):
first = False
ndvi_max = np.full((data_ch02.shape[0],data_ch02.shape[1]),-9999)
# Keep the maximuns, ignoring the nans
ndvi_max = np.fmax(data,ndvi_max)
# Remove low values from the accumulation
ndvi_max[ndvi_max < 0.1] = np.nan
# Choose the plot size (width x height, in inches)
plt.figure(figsize=(15,15))
# Use the Geostationary projection in cartopy
ax = plt.axes(projection=ccrs.Geostationary(central_longitude=-75.0, satellite_height=35786023.0))
# Add a land mask
ax.stock_img()
land = ax.add_feature(cfeature.LAND, facecolor='gray', zorder=1)
#-----------------------------------------------------------------------------------------------------------
# Define the coordinates to extract the data (lat,lon) <- pairs
coordinates = [('P1', 0,-60), ('P2', -5,-70), ('P3', -10,-55), ('P4', -20,-50)]
for label, lat, lon in coordinates:
# Reading the data from a coordinate
lat_point = lat
lon_point = lon
# Convert lat/lon to grid-coordinates
lat_ind, lon_ind = geo2grid(lat_point, lon_point, file)
NDVI_point = (ndvi_max[lat_ind - ury, lon_ind - llx]).round(2)
# Adding the data as an annotation
# Add a circle
ax.plot(lon_point, lat_point, 'o', color='red', markersize=5, transform=ccrs.Geodetic(), markeredgewidth=1.0, markeredgecolor=(0, 0, 0, 1), zorder=8)
# Add a text
txt_offset_x = 0.8
txt_offset_y = 0.8
plt.annotate(label + "\n" + "Lat: " + str(lat_point) + "\n" + "Lon: " + str(lon_point) + "\n" + "NDVI: " + str(NDVI_point) + '',
xy=(lon_point + txt_offset_x, lat_point + txt_offset_y), xycoords=ccrs.PlateCarree()._as_mpl_transform(ax),
fontsize=10, fontweight='bold', color='gold', bbox=dict(boxstyle="round",fc=(0.0, 0.0, 0.0, 0.5), ec=(1., 1., 1.)), alpha = 1.0, zorder=9)
#-----------------------------------------------------------------------------------------------------------
# Plot the image
img = ax.imshow(ndvi_max, origin='upper', vmin=vmin, vmax=vmax, extent=img_extent, cmap=cmap, zorder=2)
# Add a shapefile
shapefile = list(shpreader.Reader('ne_10m_admin_1_states_provinces.shp').geometries())
ax.add_geometries(shapefile, ccrs.PlateCarree(), edgecolor='dimgray',facecolor='none', linewidth=0.3, zorder=4)
# Add coastlines, borders and gridlines
ax.coastlines(resolution='10m', color='black', linewidth=0.8, zorder=5)
ax.add_feature(cartopy.feature.BORDERS, edgecolor='black', linewidth=0.5, zorder=6)
ax.gridlines(color='gray', alpha=0.5, linestyle='--', linewidth=0.5, xlocs=np.arange(-180, 180, 5), ylocs=np.arange(-90, 90, 5), zorder=7)
# Add a colorbar
plt.colorbar(img, label='Normalized Difference Vegetation Index', extend='both', orientation='vertical', pad=0.05, fraction=0.05)
# Extract date
date = (datetime.strptime(file.time_coverage_start, '%Y-%m-%dT%H:%M:%S.%fZ'))
# Add a title
plt.title('GOES-16 NDVI ' + date.strftime('%Y-%m-%d %H:%M') + ' UTC', fontweight='bold', fontsize=10, loc='left')
plt.title('Reg.: ' + str(extent) , fontsize=10, loc='right')
# Save the image
plt.savefig(f'{output}/G16_NDVI_{yyyymmddhhmn}.png', bbox_inches='tight', pad_inches=0, dpi=300)
# Show the image
plt.show()
# Increment the date_loop
date_loop = date_loop + timedelta(days=1)

GOES-16 Imagery + NHC Tracking and Forecast (MARN El Salvador)

Hi community,

William Abarca, from MARN (El Salvador’s Ministry of Environment and Natural Resources), kindly shared the examples below, where he is plotting the NHC Tracking and Forecast overlayed with GOES-16 Bands 02 (0.64 μm), 13 (10.35 μm), the True Color RGB and the Rainfall Rate / Quantitative Precipitation Estimate. MARN is receiving data via GEONETCast-Americas and is using SHOWCast for processing and visualization.

True Color RGB Composite overlayed with the NHC Tracking and Forecast – MARN El Salvador
GOES-16 Rainfall Rate / QPE product overlayed with the NHC Tracking and Forecast – MARN El Salvador
MARN El Salvador – SHOWCast Interface

Thanks for sharing William!

Community Development.png

This Blog series shows the products developed by the community in the Americas. Most of them refers to data received from GNC-A, however, we also post the development with data received from other means. Please find below the other posts from this series: