Note
This page was generated from a jupyter notebook.
Using the HackCalculator Component¶
Background¶
Hack’s Law refers to a commonly observed power-law relationship between the length of the longest stream in a drainage basin, \(L\), and the area that it drains, \(A\):
where \(h\) is commonly in the neighborhood of 0.5 or 0.6. It is named for the American geomorphologist John Hack (see Hack, 1957). A value of 0.5 represents perfect geometry similarity between the areal measure, \(A\), and the embedded length \(L\). Montgomery and Dietrich (1992) noted a useful “rule of thumb” empirical relationship:
which says that, roughly speaking, the surface area of a typical drainage basin is on the order of 1/3 times the square of its longest stream length. But individual drainage basins often deviate somewhat from this idealized behavior, and often it is useful to extract the values of \(C\) and \(h\) for particular basins.
Component overview¶
The HackCalculator
provides estimates of the best-fit \(C\) and \(h\) values for one or more drainage basins within a given area represented by a digital elevation model, which is contained in a grid field called topographic__elevation
. The component requires a grid that includes the following fields:
topographic__elevation
(node) elevation data for the region of interestdrainage_area
(node) contributing drainage area for each nodeflow__receiver_node
(node) ID of the receiver (node that receives flow) for each nodeflow__link_to_receiver_node
(node) ID of the link connecting node and receiver for each nodeflow__upstream_node_order
(node) array of node IDs in downstream-to-upstream order
Apart from topographic__elevation
, each of these fields are created and calculated by running the FlowAccumulator
component.
The component uses the ChannelProfiler
component to calculate cumulative downstream length for one or more channels in the terrain. Parameters for the ChannelProfiler
may be given as arguments to HackCalculator
.
When run (using the method calculate_hack_parameters
), the component creates a Pandas DataFrame called hack_coefficient_dataframe
. It is a pandas dataframe with one row for each basin for which Hack parameters are calculated. Thus, there are as many rows as the number of watersheds identified by the ChannelProfiler.
The dataframe has the following index and columns:
Index
basin_outlet_id: The node ID of the watershed outlet where each set of Hack parameters was estimated.
Columns
A_max: The drainage area of the watershed outlet.
C: The Hack coefficient as defined in the equations above.
h: The Hack exponent as defined in the equations above.
If you pass the argument save_full_df=True
, HackCalculator
will generate an additional DataFrame called full_hack_dataframe
. It is pandas dataframe with a row for every model grid cell used to estimate the Hack parameters. It has the following index and columns:
Index
node_id: The node ID of the model grid cell.
Columns
basin_outlet_id: The node IDs of watershed outlet
A: The drainage are of the model grid cell.
L_obs: The observed distance to the divide.
L_est: The predicted distance to divide based on the Hack coefficient fit.
Imports and inline docs¶
First, import what we’ll need:
[ ]:
from landlab import RasterModelGrid
from landlab.components import FlowAccumulator, HackCalculator
from landlab.io import esri_ascii
The docstring describes the component and provides some simple examples:
[ ]:
print(HackCalculator.__doc__)
The __init__
docstring lists the parameters:
[ ]:
print(HackCalculator.__init__.__doc__)
Example 1¶
In this example, we read in a small digital elevation model (DEM) from NASADEM for an area on the Colorado high plains (USA) that includes a portion of an escarpment along the west side of a drainage known as West Bijou Creek (see Rengers & Tucker, 2014).
The DEM file is in ESRI Ascii format, but is in a geographic projection, with horizontal units of decimal degrees. To calculate slope gradients properly, we’ll first read the DEM into a Landlab grid object that has this geographic projection. Then we’ll create a second grid with 30 m cell spacing (approximately equal to the NASADEM’s resolution), and copy the elevation field from the geographic DEM. This isn’t a proper projection of course, but it will do for purposes of this example.
We use the lazy_load
function rather than load
function because we just want to just read the metadata from the data file needed to create a new grid. We will use this metadata to create a new grid for each of the examples below.
[ ]:
with open("west_bijou_escarpment_snippet.asc") as fp:
grid_info, data = esri_ascii.lazy_load(fp, name="topographic__elevation", at="node")
[ ]:
grid = RasterModelGrid(grid_info.shape, xy_spacing=30.0)
grid.at_node["topographic__elevation"] = data
[ ]:
grid.imshow("topographic__elevation", colorbar_label="Elevation (m)")
Next, we run the HackCalculator
on this DEM. First we need to instantiate and run FlowAccumulator
to calculate flow directions and drainage area.
[ ]:
# instatiated and run flow accumulator
fa = FlowAccumulator(
grid,
flow_director="FlowDirectorD8", # use D8 routing
depression_finder="LakeMapperBarnes", # pit filler
method="D8", # pit filler use D8 too
redirect_flow_steepest_descent=True, # re-calculate flow dirs
reaccumulate_flow=True, # re-calculate drainagea area
)
fa.run_one_step() # run the flow accumulator
# instantiate and run HackCalculator component
hc = HackCalculator(grid)
hc.calculate_hack_parameters()
Here is the resulting DataFrame, containing the area of the largest drainage basin and its corresponding \(C\) and \(h\) values:
[ ]:
hc.hack_coefficient_dataframe
You can access the embedded ChannelProfiler
via HackCalculator.profiler
:
The ChannelProfiler
data is an ordered dict, in this case containing data for one watershed: the one that drains to node 6576 (for details see the reference documentation and tutorial resources for ChannelProfiler
).
For this example, we might wish to visualize the main channel for which the Hack coefficient and exponent were calculated. We can do that with the profiler’s plot_profiles_in_map_view
method:
[ ]:
hc.profiler.plot_profiles_in_map_view(colorbar_label="Elevation (m)")
The component also provides a node field called distance_to_divide
that, as the name implies, contains the streamwise distance between a node and its source at a drainage divide:
[ ]:
grid.imshow("distance_to_divide", colorbar_label="Distance from drainage divide (m)")
Example 2: full data frame¶
The next example is the same as the first, but here we request and examine the “full dataframe”:
[ ]:
# create grid and copy DEM into it
grid = RasterModelGrid(grid_info.shape, xy_spacing=30.0)
grid.at_node["topographic__elevation"] = data
# instatiated and run flow accumulator
fa = FlowAccumulator(
grid,
flow_director="FlowDirectorD8", # use D8 routing
depression_finder="LakeMapperBarnes", # pit filler
method="D8", # pit filler use D8 too
redirect_flow_steepest_descent=True, # re-calculate flow dirs
reaccumulate_flow=True, # re-calculate drainagea area
)
fa.run_one_step() # run the flow accumulator
# instantiate and run HackCalculator component
hc = HackCalculator(grid, save_full_df=True)
hc.calculate_hack_parameters()
[ ]:
hc.hack_coefficient_dataframe
[ ]:
hc.full_hack_dataframe
Example 3: multiple watersheds¶
By default, the ChannelProfiler
extracts data from just one watershed, which is why the above example reports Hack parameters for just one basin. Here we re-run the analysis with five basins.
[ ]:
# create grid and copy DEM into it
grid = RasterModelGrid(grid_info.shape, xy_spacing=30.0)
grid.at_node["topographic__elevation"] = data
# instatiated and run flow accumulator
fa = FlowAccumulator(
grid,
flow_director="FlowDirectorD8", # use D8 routing
depression_finder="LakeMapperBarnes", # pit filler
method="D8", # pit filler use D8 too
redirect_flow_steepest_descent=True, # re-calculate flow dirs
reaccumulate_flow=True, # re-calculate drainagea area
)
fa.run_one_step() # run the flow accumulator
# instantiate and run HackCalculator component
hc = HackCalculator(grid, number_of_watersheds=5)
hc.calculate_hack_parameters()
[ ]:
hc.hack_coefficient_dataframe
[ ]:
hc.profiler.plot_profiles_in_map_view(colorbar_label="Elevation (m)")
Example 4: multiple channels per basin¶
So far, we have only performed the calculation on the main channel in each drainage basin. We can operate on all the channels in each basin by setting the ChannelProfiler
parameter main_channel_only
to False
. While we’re at it, we will also specify a drainage area threshold for channels of 20,000 m\(^2\).
[ ]:
# create grid and copy DEM into it
grid = RasterModelGrid(grid_info.shape, xy_spacing=30.0)
grid.at_node["topographic__elevation"] = data
# instatiated and run flow accumulator
fa = FlowAccumulator(
grid,
flow_director="FlowDirectorD8", # use D8 routing
depression_finder="LakeMapperBarnes", # pit filler
method="D8", # pit filler use D8 too
redirect_flow_steepest_descent=True, # re-calculate flow dirs
reaccumulate_flow=True, # re-calculate drainagea area
)
fa.run_one_step() # run the flow accumulator
# instantiate and run HackCalculator component
hc = HackCalculator(
grid,
number_of_watersheds=5,
main_channel_only=False,
minimum_channel_threshold=2.0e4,
)
hc.calculate_hack_parameters()
[ ]:
hc.hack_coefficient_dataframe
[ ]:
hc.profiler.plot_profiles_in_map_view(colorbar_label="Elevation (m)")
References¶
Hack, J. T. (1957). Studies of longitudinal stream profiles in Virginia and Maryland. Geological Survey Professional Paper 294-B. US Government Printing Office.
Montgomery, D. R., & Dietrich, W. E. (1992). Channel initiation and the problem of landscape scale. Science, 255(5046), 826-830.