Cookbook and Examples

Here we provide a few example work flows for common FCPG tasks.

Input Data

To produce a basic FCPG you will need the following data for the same geographic area:

  • A Flow Direction Raster (FDR), either in ESRI or TauDEM format.

  • A parameter grid. (i.e. DAYMET precipitation, or land cover).

    • Note that the resolution and coordinate reference system (CRS) do not need to match the FDR. However, the data extent should completely overlap the FDR for accurate results.

  • A .shp Watershed Boundary Dataset with unique basin IDs. Note that the tools expect HUC12 IDs by default (optional).

Example 1 - Make a basic precipitation FCPG

Here we use local gridded precipitation data to make a FCPG using the pysheds engine. We then save the result locally. Note that all fcpgtools outputs are xarray.DataArray objects.

import fcpgtools
from pathlib import Path

# get in/out directory paths
# note: replace with your own paths
in_data_dir = Path.cwd() / Path('in_data')
out_data_dir = Path.cwd() / Path('out_data')

# get FDR data path
fdr_tif_path = in_data_dir / Path('validation_upstream_fdr.tif')

# make a flow accumulation raster
flow_accum = fcpgtools.accumulate_flow(
   d8_fdr=fdr_tif_path,
   engine='pysheds',
)

# get precipitation data path
precipitation_tif_path = in_data_dir / Path('validation_daymet_an_P_2017.tif')

# align the parameter grid with the FDR
aligned_precip = fcpgtools.align_raster(
   parameter_raster=precipitation_tif_path,
   d8_fdr=fdr_tif_path,
   resample_method='bilinear',
)

# make a precipitation accumulation raster
precip_accum = fcpgtools.accumulate_parameter(
   d8_fdr=fdr_tif_path,
   parameter_raster=aligned_precip,
   engine='pysheds',
)

# create a FCPG and save locally
out_fcpg_path = out_data_dir / Path('precipitation_fcpg.tif')

precip_fcpg = fcpgtools.make_fcpg(
    param_accum_raster=precip_accum,
    fac_raster=flow_accum,
    out_path=out_fcpg_path,
)

# plot the output (works in a notebook environment)
precip_fcpg.plot()

Example 2 - Cascade accumulated precipitation from one basin to another

Here we continue from example 1 by cascading accumulated precipitation at the pour point of the upstream basin to the next basin downstream. This can be thought of as updating the boundary conditions of a basins parameter accumulation calculation.

In this example we use a watershed defined at the HUC4 level, however, any shapefile can be used to define watershed boundaries as long as a unique identifier is passed into the basin_id_field parameter of find_basin_pour_points() and use_huc4=False.

# pull in HUC12 basin boundaries (will be converted to HUC4 later)
huc12_basins_shp_path = in_data_dir / Path('basin_boundaries.shp')

huc12_basins_shp = fcpgtools.load_shapefile(huc12_basins_shp_path)

# get the HUC4 basin pour point values from our example 1 precipitation accumulation
pour_point_locations_dict = fcpgtools.find_basin_pour_points(
   fac_raster=precip_accum, 
   basins_shp=huc12_basins_shp, 
   basin_id_field='HUC12', 
   use_huc4=True,
)

pour_point_values_dict = fcpgtools.get_pour_point_values(
   pour_points_dict=pour_point_locations_dict, 
   accumulation_raster=precip_accumulation,
)

# get  downstream FDR data path
downstream_fdr_path = in_data_dir / Path('validation_downstream_fdr.tif')

# get upstream and downstream precipitation data paths
# NOTE: this is for explanatory purposes only, downstream basin precipitation data is not stored in this repo!
downstream_precip_data_path = in_data_dir / Path('downstream_daymet_P_2017.tif')

# align the downstream parameter grid with the downstream FDR
aligned_ds_precip = fcpgtools.align_raster(
   parameter_raster=downstream_precip_data_path,
   d8_fdr=downstream_fdr_path,
   resample_method='bilinear',
)

# accumulate downstream accumulation with the cascaded precipitation values
ds_precip_accum = fcpgtools.accumulate_parameter(
   d8_fdr=downstream_fdr_path,
   parameter_raster=aligned_ds_precip,
   engine='pysheds',
   upstream_pour_points=pour_point_locations_dict,
)

Example 3 - Accumulate land cover

Here we use a categorical land cover raster as the parameter grid. Note that the output will be a multi-band xarray.DataArray object where each band stores the accumulation of an unique land cover class. Additionally, by using the optional categories_dict of binarize_categorical_raster() we can add string labels to each output land cover class accumulation band.

# get land use data path
land_use_tif_path = in_data_dir / Path('NALCMS_2015.tif')

# define labels for each land cover class of interest
landcover_classes = {
    1: 'evergreen forest',
    7: 'tropical shrubland',
    8: 'temperate shrubland',
    9: 'tropical grassland',
    10: 'temperate grassland',
    14: 'wetland',
    15: 'cropland',
    16: 'barren',
    17: 'urban',
    18: 'open water',
}

# prepare the categorical raster for accumulation and ignore 'open_water'
land_cover_raster = fcpgtools.(
   cat_raster=land_use_tif_path, 
   categories_dict=landcover_classes, 
   ignore_categories=['open water'], 
)

# align the parameter grid with the FDR
# NOTE: resample_method should == 'nearest' when accumulating categorical rasters!
aligned_land_cover = fcpgtools.align_raster(
   parameter_raster=land_cover_raster,
   d8_fdr=fdr_tif_path,
   resample_method='nearest',
)

# make a land cover accumulation raster
precip_accum = fcpgtools.accumulate_parameter(
   d8_fdr=fdr_tif_path,
   parameter_raster=aligned_land_cover,
   engine='pysheds',
)

Example 4 - Use TauDEM to make a decayed accumulation raster

Here we build off the outputs from example 1 and create a precipitation FCPG accumlation values are “decayed” by their distance from the nearest stream. We then sample the output decayed FCPG at a multiple points.

  • Her we define “streams” as cells having >= 200 cells upstream.

  • Also note that this functionality is currently only enabled using engine='taudem'.

# create a distance to stream raster
# NOTE: here we also demo how to use taudem kwargs to customize cmd line execution
dist2stream_raster = fcpgtools.distance_to_stream(
   d8_fdr=fdr_tif_path, 
   fac_raster=flow_accum, 
   accum_threshold=200, 
   engine='taudem', 
   kwargs={'cores': 8},
)

# create a decay weighting raster
# NOTE: here we use a medium decay factor of 2
decay_weights = fcpgtools.make_decay_raster(
   distance_to_stream_raster=dist2stream_raster, 
   decay_factor=2,
)

# create a decayed precipitation accumulation raster using the previously "aligned" data
decay_precip_accum = fcpgtools.decay_accumulation(
   d8_fdr=fdr_tif_path, 
   decay_raster=, 
   engine='taudem', 
   parameter_raster=aligned_precip,
   kwargs={'cores': 8},
)

# create path to save output locally
out_decay_fcpg_path = out_data_dir / Path('decay_precipitation_fcpg.tif')

# create a decayed precipitation FCPG
decay_precip_fcpg = fcpgtools.make_fcpg(
   param_accum_raster=decay_precip_accum, 
   fac_raster=flow_accum, 
   out_path=out_decay_fcpg_path,
)

# create a dictionary of type=PourPointLocationsDict to define points of interest
sample_points_dict = {
   pour_point_ids=[
      'gage1', 
      'gage2', 
      'gage3',
   ]
   pour_point_coords=[
      (31.4324, -45.4325), 
      (31.9931, -45.8988), 
      (32.004, -45.1235),
   ]
}

# sample the decay FCPG at our points of interets
sampled_fcpg_dict = fcpgtools.get_pour_point_values(
   pour_points_dict=sample_points_dict, 
   accumulation_raster=decay_precip_fcpg,
)

# note that the output would have the following form for a 6 month/band precipitation raster
print(sampled_fcpg_dict) -> {
   pour_point_ids=[
      'gage1', 
      'gage2', 
      'gage3',
   ]
   pour_point_coords=[
      (31.4324, -45.4325), 
      (31.9931, -45.8988), 
      (32.004, -45.1235),
   ]

   # NOTE: the list position index corresponds to precipitation raster band index
   pour_point_values=[
      [12.3, 13.4, 25.1, 40.1, 20.2, 11.9],
      [7.4, 2.0, 15.6, 15.5, 14.7, 0.8],
      [9.1, 10.4, 15.6, 20.1, 22.4, 0.4],
   ]
}