r.stream.extract(1grass)

πŸ’§ r.stream.extract πŸ’§

okay bestie, buckle UP, because this is r.stream.extract from the GRASS User's Manual, and she's a whole hydrology icon. she extracts stream networks. that's her thing. she does it in raster and vector. she's booked and busy πŸ’…


✨ NAME ✨

r.stream.extract β€” performs stream network extraction 🏞️

🏷️ KEYWORDS 🏷️

raster, hydrology, stream network


πŸ“œ SYNOPSIS πŸ“œ

r.stream.extract
r.stream.extract --help
r.stream.extract elevation=name  [accumulation=name]   [depression=name]
threshold=float  [d8cut=float]   [mexp=float]   [stream_length=integer]
[memory=memory in MB]   [stream_raster=name]   [stream_vector=name]
[direction=name]   [--overwrite]  [--help]  [--verbose]  [--quiet]
[--ui]

🚩 Flags 🚩

  • --overwrite β€” let those output files just steamroll over existing files, no cap. overwrite era 😌
  • --help β€” prints the usage summary, aka the cheat sheet πŸ“
  • --verbose β€” verbose module output, she's chatting πŸ’¬
  • --quiet β€” quiet module output, she's giving silent treatment 🀫
  • --ui β€” force launching the GUI dialog, for the visual girlies πŸŽ€

πŸŽ›οΈ Parameters πŸŽ›οΈ

  • elevation=name [required] β€” name of the input elevation raster map. this one's mandatory, she's the main character ⛰️
  • accumulation=name β€” name of the input accumulation raster map. if you hand her this, stream extraction will use your provided accumulation instead of calculating it fresh from scratch. no cap, she'll take your word for it 🫢
  • depression=name β€” name of the input raster map with real depressions. streams will not be routed out of real depressions, periodt πŸ™…β€β™€οΈ
  • threshold=float [required] β€” minimum flow accumulation for streams. must be > 0, so don't be trying anything cute with zero or negatives, bestie 🚫
  • d8cut=float β€” use SFD above this threshold. if accumulation is larger than d8cut, SFD gets used instead of MFD. heads up: this only applies if no accumulation map is given ⚠️
  • mexp=float β€” Montgomery exponent for slope, and she's disabled when it's 0. Montgomery mode means: accumulation is multiplied with pow(slope,mexp) and then compared with threshold. Default: 0 πŸ“
  • stream_length=integer β€” delete stream segments shorter than stream_length cells. this only applies to first-order stream segments (springs/stream heads). Default: 0 βœ‚οΈ
  • memory=memory in MB β€” maximum memory to be used (in MB). it's the cache size for raster rows. Default: 300 🧠
  • stream_raster=name β€” name for the output raster map with unique stream ids πŸ—ΊοΈ
  • stream_vector=name β€” name for the output vector map with unique stream ids πŸ“
  • direction=name β€” name for the output raster map with flow direction 🧭

πŸ’– DESCRIPTION πŸ’–

r.stream.extract extracts streams in both raster and vector format, from a required input elevation map plus an optional input accumulation map. two formats, one queen. slay ✨


πŸ“ NOTES πŸ“

NULL (nodata) cells in the input elevation map? ignored, bestie, she's not looking at them. but zero and negative values are valid elevation data, so don't come for them πŸ’€. gaps in the elevation map that fall inside your area of interest have to be filled beforehand β€” like, with r.fillnulls β€” otherwise you get distortions. fix it first, thank me later 🩹

every non-NULL and non-zero cell in the depression map gets treated as a real depression. streams will not be routed out of depressions. and before you ask: if an area is marked as a depression but the elevation model has no depression there, streams will not stop there. lowkey important. also, if you give her both a flow accumulation map and a map with real depressions, those two have to match up β€” in the sense that flow must not be distributed out of the indicated depressions. honestly? if you're providing a depression map, it's recommended to just let her compute flow accumulation internally 🫑

the threshold option defines the minimum (optionally modified) flow accumulation value that will kick off a new stream. if Montgomery's method for channel initiation is in play, the cell value of the accumulation input map is multiplied by (tan(local slope))mexp and then compared to threshold. if mexp is given, then the method of Montgomery and Foufoula-Georgiou (1993) is used to initiate a stream with this value β€” yep, same deal: cell value of the accumulation input map times (tan(local slope))mexp, then compared against threshold. hit or exceed it, and boom, a new stream is born πŸ‘ΆπŸŒŠ. the default value 0 disables Montgomery. Montgomery and Foufoula-Georgiou (1993) generally recommend using 2.0 as the exponent. mexp values closer to 0 produce streams more similar to streams extracted with Montgomery disabled. larger mexp values decrease the number of streams in flat areas and increase the number of streams in steep areas. and if weight is given, the weight is applied first, no skipping the line πŸ’…

the d8cut option defines the minimum amount of overland flow (accumulation) at which SFD (D8) will be used instead of MFD (FD8) to calculate flow accumulation. only applies when no accumulation map is provided. setting it to 0 disables MFD completely πŸ›‘

the stream_length option defines the minimum stream length, in number of cells, for first-order (head/spring) stream segments. all first-order stream segments shorter than stream_length get deleted. bye bye tiny streams πŸ‘‹

the output direction raster map contains flow direction for all non-NULL cells in the input elevation. flow direction is of D8 type, with a range of 1 to 8. multiply those values by 45 and you get degrees CCW from East πŸ”„. flow direction was adjusted during thinning β€” taking shortcuts and skipping cells that got eliminated by the thinning procedure. she's efficient like that 😌

🌊 Stream extraction 🌊

if no accumulation input map is provided, flow accumulation gets determined with a hydrological analysis similar to r.watershed. the algorithm is MFD (FD8) after Holmgren 1994, same as r.watershed. the threshold option determines the number of streams and the detail level of the stream networks. whenever flow accumulation reaches threshold, a new stream starts and gets traced downstream to its outlet point. and just like r.watershed, flow accumulation is calculated as the number of cells draining through a cell πŸ’§

if accumulation is given, then the accumulation values from your provided accumulation map get used, and nothing gets calculated from the input elevation map. in that case the elevation map must be exactly the same map used to calculate accumulation β€” no substitutions, bestie. if accumulation was calculated with r.terraflow, then you must use the filled elevation output of r.terraflow. also, the current region should be aligned to the accumulation map. flow direction gets calculated from elevation first, and then adjusted to accumulation. and good news: you don't have to provide accumulation as the number of cells β€” it can also be the optionally adjusted or weighed total contributing area in square meters, or literally any other unit πŸ’«. but when an original flow accumulation map is adjusted or weighed, that adjustment or weighing should not convert valid accumulation values into NULL (nodata) values. don't do that πŸ™ˆ

and if you get the error message ERROR: Accumulation raster map is NULL but elevation map is not NULL, that means the computational region has to be carefully adjusted to exclude NULL pixels in the accumulation raster map before stream extraction. read the room and fix your region 🚨

βš–οΈ Weighed flow accumulation βš–οΈ

flow accumulation can be calculated first β€” with r.watershed, for example β€” and then modified before you feed it to r.stream.extract. in its general form, a weighed accumulation map is generated by first creating a weighing map, and then multiplying the accumulation map with the weighing map using r.mapcalc. and it is highly recommended to evaluate the weighed flow accumulation map first, before using it as input for r.stream.extract. do the vibe check first, fr fr πŸ‘€

this lets you, for example, decrease the number of streams in dry areas and increase the number of streams in wet areas β€” just set the weight to smaller than 1 in dry areas and larger than 1 in wet areas. obsessed with the flexibility honestly πŸŒ΅πŸ’§

another possibility: restrict channel initiation to valleys determined from terrain morphology. valleys can be determined with r.param.scale method=crosc (cross-sectional or tangential curvature). curvature values < 0 indicate concave features, i.e. valleys. and the size of the processing window determines whether narrow or broad valleys get identified (see example below) πŸ‘‘

🎯 Defining a region of interest 🎯

the stream extraction procedure can be restricted to a certain region of interest β€” a subbasin, say β€” by setting the computational region with g.region and/or creating a mask with r.mask. such a region of interest should be a complete catchment area, and complete here means the complete area upstream of an outlet point is included and buffered with at least one cell. no half-finished catchments, please, she will notice πŸ”

πŸ“€ Stream output πŸ“€

the output raster and vector contain stream segments with unique IDs. note that these IDs are different from the IDs assigned by r.watershed β€” don't get them confused, they're not twins, they're strangers πŸ’€. the vector output also contains points at the location of the start of a stream segment, at confluences, and at stream network outlet locations πŸ“Œ

output stream_raster raster map stores extracted streams. cell values encode a unique ID for each stream segment πŸ†”

output stream_vector vector map stores extracted stream segments and points. points get written at the start location of each stream segment and at the outlet of a stream network. in layer 1, categories are unique IDs, identical to the cell value of the raster output. the attribute table for layer 1 holds information about the type of stream segment β€” start segment, or intermediate segment with tributaries β€” and about the stream network this stream or node belongs to. columns are cat int,stream_type varchar(),type_code int,network int. the network attribute is the network ID of the stream/node. the encoding for type_code is 0 = start, 1 = intermediate. in layer 2, categories are identical to type_code in layer 1, with the additional category 2 = outlet for outlet points. points with category 1 = intermediate in layer 2 are at the location of confluences. whew. iconic data structure, not gonna lie πŸ’–

🌊 EXAMPLE (aka the part where we actually do the thing)

Bestie, this whole example is built on the elevation map elev_ned_30m from the North Carolina sample dataset ✨, and it uses valleys that r.param.scale found to weigh an accumulation map that r.watershed made. It's giving very much "combine the girlies, get a better result" energy πŸ”₯

First up, set the region and get your layers sorted:

# set region
g.region -p raster=elev_ned_30m@PERMANENT
# calculate flow accumulation
r.watershed ele=elev_ned_30m@PERMANENT acc=elev_ned_30m.acc

Now we go hunting for valleys at three different sizes β€” narrow, a bit broader, and broad πŸ’… because curvature is literally the moment here:

# curvature to get narrow valleys
r.param.scale input=elev_ned_30m@PERMANENT output=tangential_curv_5 size=5 method=crosc
# curvature to get a bit broader valleys
r.param.scale input=elev_ned_30m@PERMANENT output=tangential_curv_7 size=7 method=crosc
# curvature to get broad valleys
r.param.scale input=elev_ned_30m@PERMANENT output=tangential_curv_11 size=11 method=crosc

Then we build the weight map, no cap 🫢:

# create weight map
r.mapcalc "weight = if(tangential_curv_5 < 0, -100 * tangential_curv_5, \
                    if(tangential_curv_7 < 0, -100 * tangential_curv_7, \
                    if(tangential_curv_11 < 0, -100 * tangential_curv_11, 0.000001)))"

Weigh the accumulation map, then steal the color table from the original because we're not about to redo that:

# weigh accumulation map
r.mapcalc expr="elev_ned_30m.acc.weighed = elev_ned_30m.acc * weight"
# copy color table from original accumulation map
r.colors map=elev_ned_30m.acc.weighed raster=elev_ned_30m.acc

Vibe check, visual edition:

  • Weight map (spatial subset with lake in the southern half)
  • Original flow accumulation map (spatial subset with lake in the southern half)
  • Weighed flow accumulation map (spatial subset with lake in the southern half)

Now display both the original and the weighed accumulation map, compare them, and only then proceed if the weighed accumulation map makes sense πŸ‘€ β€” like, don't just rawdog it, actually look at your data first.

# extract streams using the original accumulation map
r.stream.extract elevation=elev_ned_30m@PERMANENT \
                 accumulation=elev_ned_30m.acc \
                 threshold=1000 \
                 stream_rast=elev_ned_30m.streams.noweight
# extract streams from weighed map
# note that the weighed map is a bit smaller than the original map
r.stream.extract elevation=elev_ned_30m@PERMANENT \
                 accumulation=elev_ned_30m.acc.weighed \
                 threshold=1000 \
                 stream_rast=elev_ned_30m.streams
πŸ‘‘ Heads up, the weighed map is a bit smaller than the original map β€” that's just a thing that happens, not a bug, not you being bad at GIS.

Display both stream maps and decide which one is more realistic. Trust your gut, bestie 🌸

  • Extracted streams from original flow accumulation map
  • Extracted streams from weighed flow accumulation map

πŸ“š REFERENCES (the receipts, periodt)

  • Ehlschlaeger, C. (1989). Using the AT Search Algorithm to Develop Hydrologic Models from Digital Elevation Data, Proceedings of International Geographic Information Systems (IGIS) Symposium '89, pp 275-281 (Baltimore, MD, 18-19 March 1989). URL: https://www.researchgate.net/publication/243781937_Using_the_AT_search_algorithm_to_develop_hydrologic_models_from_digital_elevation_data
  • Holmgren, P. (1994). Multiple flow direction algorithms for runoff modelling in grid based elevation models: An empirical evaluation. Hydrological Processes Vol 8(4), pp 327-334. DOI: 10.1002/hyp.3360080405
  • Montgomery, D.R., Foufoula-Georgiou, E. (1993). Channel network source representation using digital elevation models. Water Resources Research Vol 29(12), pp 3925-3934.

πŸ‘€ SEE ALSO (the friend group)

r.mapcalc, r.param.scale, r.stream.channel (Addon), r.stream.distance (Addon), r.stream.order (Addon), r.stream.segment (Addon), r.stream.slope (Addon), r.stream.snap (Addon), r.stream.stats (Addon), r.terraflow, r.thin, r.to.vect, r.watershed

Also, go look at the r.streams.* modules wiki page β€” iykyk πŸ’œ


✨ AUTHOR

Markus Metz πŸ’–


πŸŽ€ SOURCE CODE

Available at: r.stream.extract source code (history)

Latest change: Friday Apr 17 01:04:26 2026 in commit: 32a143358df26b2e518b03c7e0d79541e8d1e5b1

Main index | Raster index | Topics index | Keywords index | Graphical index | Full index

Β© 2003-2026 GRASS Development Team, GRASS 8.5.0 Reference Manual


okay and that's the whole page bestie πŸ’… This one came to you from GRASS 8.5.0 β€” the manual footer says GRASS 8.5.0 down at the bottom left and r.stream.extract(1grass) on the right, with the source/version and date info living right up in that SOURCE CODE section. Go extract some streams, you absolute icon πŸ¦‹βœ¨

πŸ“¦ where this page comes from