The National Center for Atmospheric Research (NCAR) supplies climate forecast data online, with four daily updates. Why not use the power of GRASS-GIS and python to produce animations of predicted rainfall?We’ll see how to subset and download the GFS (Global Forecasting System) from the
NCAR Research Data Archive . These data are stored in grib2 format. No worries GDAL reads climate data in the various weather data formats including grib2. So we can import the data directly as rasters into GRASS, create a set of maps, and then, using the python
imageio library, we output the precipitation forecasts as an animated gif. For this tutorial I use the GFS 0.25 degree forecasts available at the link above. (Registration on the NCAR website is required). Then through the web interface I subset the data for a certain date range, a spatial extent, and particular variables. Here’s the procedure: I click on “Get a Subset” and choose both the
Temporal Selection (date range) of interest, and the
Precipitation Rate variable. GFS forecasts are published four times daily, and for this example I will go back to a storm event from October 2015. After clicking “Continue” the next screen allows me to choose which products I want and to determine the spatial extent. I choose all the
6-hour Averages from initial+0 up to initial+144 hours. This choice will prepare 24 forecast files (one every 6 hours for 6 days) for each initialization time in the date range that I selected. So if my date range covers 2 days (with 4 GFS forecasts initialized each day) then I will be downloading 24×8=192 data files. Now scroll down to the map under
Spatial Selection , zoom to the area of interest, and draw a box to set the longitude and latitude extents. Once all the options set, I click on “Submit Request”. In a short while an email arrives (at my NCAR registration email address) with the download link for the chosen files. Browse to the link in the email message, select all files in the list, and click on one of the download options: either directly download a tar file, or get a shell or perl script. I should mention that the above procedure can be scripted and the whole selection and download can be handled with a set of python scripts offered by NCAR. Check the
RDA apps webpage for details. Now, with the GFS data at hand, I’m ready to start GRASS, and open a python session. The GFS data are in Longitude/Latitude, so I start GRASS in a longlat WGS84 location. Be sure you have
imageio installed. The script below covers the basics, but it will need to be stitched to fit each particular situation.
import grass.script as gscript
import imageio
import os, glob
# Some initial variables
# cc is the GFS cycle, either ’00’, ’06’,’12’, or ’18’
cc = ’06’
# storm_date is the date of the storm event in YYYYMMDD format
storm_date = ‘20151021’
# The directory that holds the GFS files, and an output directory
input_dir = os.path.join(os.path.expanduser(‘~’), ‘Downloads/GFS’)
output_dir = os.path.join(os.path.expanduser(‘~’), ‘Documents’)
# A list for animation frames
frames = []
# and the final animated gif file:
anim = os.path.join(output_dir,
‘anim_’+storm_date+cc+’.gif’)
# Now create a list of GFS files to process
# Since there might be several dates and
# four daily cycles for each date in the Download directory,
# we use the storm_date and cycle to build a filter expression
# All files begin with "gfs.0p25." then the date, and cycle,
# and last the forecast hour
glob_expr = ‘gfs.0p25.’+storm_date+cc+’*’
gfs_paths = glob.glob(os.path.join(input_dir, glob_expr))
# Make sure the list of files is sorted
gfs_paths.sort()
# Start processing loop to import grib2 files
for gfs in gfs_paths:
# Create a suitable name for the GRASS raster,
# (dot in names are not SQL compliant,
# so GRASS does not accept ‘.’ in map names, replace with ‘_’)
gfs_parts = os.path.basename(gfs).split(‘.’)
gfs_rast = ‘gfs_’+gfs_parts[2]+’_’+gfs_parts[3]
# Now import the GFS grib2 file to a tmp raster
gscript.run_command(‘r.in.gdal’, input=gfs,
output=’tmp_gfs_rast’, band=1, quiet=True, flags=’o’, overwrite=True)
# As always, be sure region settings match the imported raster
gscript.run_command(‘g.region’, raster=’tmp_gfs_rast’, quiet=True)
# The units in GFS "Precipitation Rate" are mm/sec.
# Multiply by 3600 to convert to mm/hr
# This creates each final gfs raster layer
expr = gfs_rast+" = tmp_gfs_rast*3600"
gscript.mapcalc(expr, overwrite=True)
# And set a uniform color ramp for the precip rasters
gscript.run_command(‘r.colors’,map=gfs_rast,
rule=’precip_colors.txt’, quiet=True)
# The file "precip_colors.txt" contains:
# 0 white
# 0.1 240:243:255
# 6 8:69:149
# 20 black
# Now create a png file from each raster
out_png = ‘precip_’+gfs_parts[2]+’_’+gfs_parts[3]+’.png’
out_path = os.path.join(output_dir, out_png)
ttl = " ".join([storm_date, cc+’Z’, gfs_parts[3]])
gscript.run_command(‘d.mon’, start=’png’, width=350,
height=400, output=out_path, quiet=True, overwrite=True)
gscript.run_command(‘d.rast’, map=gfs_rast, quiet=True)
# Add a vector map of coastlines
gscript.run_command(‘d.vect’, map=’coastline’, color=’black’,
fill_color=’none’, quiet=True)
gscript.run_command(‘d.text’, text=ttl, at=’3,12′, align=’ul’,
size=3, color="100:1:1", font=’DejaVu Sans:Bold’, flags=’pb’)
gscript.run_command(‘d.legend’, rast=gfs_rast, at=’5,25,85,93′,
range=’0,8′, font=’Liberation Mono:Regular’, quiet=True)
gscript.run_command(‘d.mon’, stop=’png’, flags="r", quiet=True)
# add png to numpy array "frames"
frames.append(imageio.imread(out_path))
# When the above loop completes, create the animation from the frames list
try:
imageio.mimwrite(anim, frames, format=’GIF’, duration=0.7)
print(‘Output GFS animation: %s saved’ % anim)
except:
print(‘Error making animation’) And here’s what I get (click to animate):
![Precipitation animations with GRASS-GIS and python]()
Precipitation 21-25/10/2015
import imageio
import os, glob
# Some initial variables
# cc is the GFS cycle, either ’00’, ’06’,’12’, or ’18’
cc = ’06’
# storm_date is the date of the storm event in YYYYMMDD format
storm_date = ‘20151021’
# The directory that holds the GFS files, and an output directory
input_dir = os.path.join(os.path.expanduser(‘~’), ‘Downloads/GFS’)
output_dir = os.path.join(os.path.expanduser(‘~’), ‘Documents’)
# A list for animation frames
frames = []
# and the final animated gif file:
anim = os.path.join(output_dir,
‘anim_’+storm_date+cc+’.gif’)
# Now create a list of GFS files to process
# Since there might be several dates and
# four daily cycles for each date in the Download directory,
# we use the storm_date and cycle to build a filter expression
# All files begin with "gfs.0p25." then the date, and cycle,
# and last the forecast hour
glob_expr = ‘gfs.0p25.’+storm_date+cc+’*’
gfs_paths = glob.glob(os.path.join(input_dir, glob_expr))
# Make sure the list of files is sorted
gfs_paths.sort()
# Start processing loop to import grib2 files
for gfs in gfs_paths:
# Create a suitable name for the GRASS raster,
# (dot in names are not SQL compliant,
# so GRASS does not accept ‘.’ in map names, replace with ‘_’)
gfs_parts = os.path.basename(gfs).split(‘.’)
gfs_rast = ‘gfs_’+gfs_parts[2]+’_’+gfs_parts[3]
# Now import the GFS grib2 file to a tmp raster
gscript.run_command(‘r.in.gdal’, input=gfs,
output=’tmp_gfs_rast’, band=1, quiet=True, flags=’o’, overwrite=True)
# As always, be sure region settings match the imported raster
gscript.run_command(‘g.region’, raster=’tmp_gfs_rast’, quiet=True)
# The units in GFS "Precipitation Rate" are mm/sec.
# Multiply by 3600 to convert to mm/hr
# This creates each final gfs raster layer
expr = gfs_rast+" = tmp_gfs_rast*3600"
gscript.mapcalc(expr, overwrite=True)
# And set a uniform color ramp for the precip rasters
gscript.run_command(‘r.colors’,map=gfs_rast,
rule=’precip_colors.txt’, quiet=True)
# The file "precip_colors.txt" contains:
# 0 white
# 0.1 240:243:255
# 6 8:69:149
# 20 black
# Now create a png file from each raster
out_png = ‘precip_’+gfs_parts[2]+’_’+gfs_parts[3]+’.png’
out_path = os.path.join(output_dir, out_png)
ttl = " ".join([storm_date, cc+’Z’, gfs_parts[3]])
gscript.run_command(‘d.mon’, start=’png’, width=350,
height=400, output=out_path, quiet=True, overwrite=True)
gscript.run_command(‘d.rast’, map=gfs_rast, quiet=True)
# Add a vector map of coastlines
gscript.run_command(‘d.vect’, map=’coastline’, color=’black’,
fill_color=’none’, quiet=True)
gscript.run_command(‘d.text’, text=ttl, at=’3,12′, align=’ul’,
size=3, color="100:1:1", font=’DejaVu Sans:Bold’, flags=’pb’)
gscript.run_command(‘d.legend’, rast=gfs_rast, at=’5,25,85,93′,
range=’0,8′, font=’Liberation Mono:Regular’, quiet=True)
gscript.run_command(‘d.mon’, stop=’png’, flags="r", quiet=True)
# add png to numpy array "frames"
frames.append(imageio.imread(out_path))
# When the above loop completes, create the animation from the frames list
try:
imageio.mimwrite(anim, frames, format=’GIF’, duration=0.7)
print(‘Output GFS animation: %s saved’ % anim)
except:
print(‘Error making animation’) And here’s what I get (click to animate):

Precipitation 21-25/10/2015