diff --git a/content/tutorials/howtos/convert-flowdir.qmd b/content/tutorials/howtos/convert-flowdir.qmd
new file mode 100644
index 0000000..996f63f
--- /dev/null
+++ b/content/tutorials/howtos/convert-flowdir.qmd
@@ -0,0 +1,263 @@
+---
+title: "How to convert flow direction between different encodings in GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [bash, beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+# Introduction
+
+This document shows how to convert flow direction encodings between several formats in GRASS. Each section displays:
+
+- **Figure Left:** Input encoding
+- **Equation:** Conversion formula
+- **Figure Right:** Output encoding
+- **GRASS Command**
+
+---
+
+## 1. Degree to 45degree
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$D = \frac{d}{45}$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "45degree = int(degree / 45)"
+```
+
+## 2. 45degree to Degree
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$d = 45 \times |D|$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "degree = abs(45degree) * 45"
+```
+
+## 3. Degree to Power2
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$p = 2^{8 - \frac{d}{45}}$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "power2 = 2^(8 - degree / 45)"
+```
+---
+
+## 4. Power2 to Degree
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$d = 45 \times (8 - \log_2 p)$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "degree = 45 * int(8 - log(power2, 2))"
+```
+
+## 5. Degree to TauDEM
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$t = 1 + \left( \frac{d}{45} \mod 8 \right)$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "taudem = 1 + (degree / 45) % 8"
+```
+
+## 6. TauDEM to Degree
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$d = 45 \times \left\{ \begin{array}{ll} t-1 & \text{if } t-1 > 0 \\ 8 & \text{otherwise} \end{array} \right.$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "degree = 45 * if(taudem - 1, taudem - 1, 8)"
+```
+
+## 7. 45degree to Power2
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$p = 2^{8 - |D|}$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "power2 = 2^(8 - abs(45degree))"
+```
+
+## 8. Power2 to 45degree
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$D = 8 - \log_2 p$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "45degree = int(8 - log(power2, 2))"
+```
+
+## 9. 45degree to TauDEM
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$t = 1 + (|D| \mod 8)$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "taudem = 1 + abs(45degree) % 8"
+```
+
+## 10. TauDEM to 45degree
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$D = \left\{\begin{array}{ll} t-1 & \text{if } t-1 > 0 \\ 8 & \text{otherwise} \end{array} \right.$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "45degree = if(taudem - 1, taudem - 1, 8)"
+```
+
+## 11. Power2 to TauDEM
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$t = 1 + \left[ (8 - \log_2 p) \mod 8 \right]$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "taudem = 1 + (8 - log(power2, 2)) % 8"
+```
+
+## 12. TauDEM to Power2
+
+::: columns
+:::: {.column width="20%"}
+{width=80%}
+::::
+
+:::: {.column width="60%"}
+$$p = 2^{(9 - t) \mod 8}$$
+::::
+
+:::: {.column width="20%"}
+{width=80%}
+::::
+:::
+```bash
+r.mapcalc "power2 = 2^((9 - taudem) % 8)"
+```
diff --git a/content/tutorials/howtos/create-empty-vector-map.qmd b/content/tutorials/howtos/create-empty-vector-map.qmd
new file mode 100644
index 0000000..8027283
--- /dev/null
+++ b/content/tutorials/howtos/create-empty-vector-map.qmd
@@ -0,0 +1,34 @@
+---
+title: "How to create an empty vector map in GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+
+
+[`v.edit`](https://grass.osgeo.org/grass-stable/manuals/v.edit.html) can create an empty vector map, but it does not create an attribute table.
+
+```bash
+# Create an empty vector map
+v.edit map=new_map tool=create
+# Add a new table
+v.db.addtable map=new_map columns="value double"
+```
diff --git a/content/tutorials/howtos/delineate-stream-networks.qmd b/content/tutorials/howtos/delineate-stream-networks.qmd
new file mode 100644
index 0000000..29c446c
--- /dev/null
+++ b/content/tutorials/howtos/delineate-stream-networks.qmd
@@ -0,0 +1,78 @@
+---
+title: "How to delineate stream networks in GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [bash, beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+# 1. Identifying stream networks without calculating hydrologic parameters
+
+Extract streams using the A* algorithm with a threshold value of 50,000 cells for stream generation:
+
+```bash
+# accumulation=flow_accum is optional
+r.stream.extract elevation=elevation threshold=50,000 stream_vector=streams accumulation=flow_accum
+```
+
+Figure 1 shows a stream network vector map created using [r.stream.extract](https://grass.osgeo.org/grass-stable/manuals/r.stream.extract.html). The stream vector properly represents flow directions.
+
+
+
+---
+
+# 2. Delineating watersheds and identifying stream networks from the same source of elevation data
+
+Calculate flow direction and accumulation, and delineate basins in the raster format:
+
+```bash
+r.watershed -a elevation=elevation threshold=50,000 accumulation=flow_accum basin=basins
+```
+
+Extract streams from the flow accumulation raster map from [r.watershed](https://grass.osgeo.org/grass-stable/manuals/r.watershed.html) so that the stream network output matches the watershed output from [r.watershed](https://grass.osgeo.org/grass-stable/manuals/r.watershed.html):
+
+```bash
+r.stream.extract elevation=elevation accumulation=flow_accum threshold=50,000 stream_vector=streams
+```
+
+---
+
+# 3. Delineating stream networks from a flow direction map
+
+[r.accumulate](https://grass.osgeo.org/grass-stable/manuals/addons/r.accumulate.html) takes a flow direction map and delineates stream networks using a threshold:
+
+```bash
+r.accumulate direction=drain_directions threshold=50000 stream=streams
+```
+
+---
+
+# 4. How not to delineate stream networks
+
+[r.watershed](https://grass.osgeo.org/grass-stable/manuals/r.watershed.html) generates a stream raster map and you may be tempted to simply convert this stream raster map to vector to identify stream networks:
+
+```bash
+r.watershed -a elevation=elevation threshold=50,000 stream=streams
+r.thin input=streams output=streams_thinned
+r.to.vect input=streams_thinned output=streams type=line
+```
+
+However, there are two problems with this method. First, the output stream vector map does not guarantee the correct directionality of stream paths. Second, if there are raster cell clumps in the stream raster map, stream loops may be generated or even incorrect stream paths can be obtained. An example is shown in Figure 2. Note that the stream vector does not fully agree with flow directions. Compare this output to Figure 1.
+
+
diff --git a/content/tutorials/howtos/dissolve-polygon.qmd b/content/tutorials/howtos/dissolve-polygon.qmd
new file mode 100644
index 0000000..79a1322
--- /dev/null
+++ b/content/tutorials/howtos/dissolve-polygon.qmd
@@ -0,0 +1,71 @@
+---
+title: "How to dissolve polygons spatially in GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [bash, beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+
+You can use the [`v.dissolve`](https://grass.osgeo.org/grass-stable/manuals/v.dissolve.html) module to dissolve polygons (areas in GRASS terms) by attribute (with `column=`) or category (without `column=`), but what if you don’t have any common attributes or categories, and want to dissolve them spatially? You cannot find a module that just does spatial dissolving in GRASS. However, it can be done!
+
+I wanted to remove small offshore islands that are not entirely one country from the [GADM 3.6](https://gadm.org/) data. The rationale behind this task is to leave islands-only countries untouched, but remove small islands that are part of an inland or bigger island country. This task was tricky because I could not just remove small areas because those areas can be inland areas. As far as I know, there are no modules that can identify offshore areas that do not touch other neighbor areas in the same layer.
+
+In this article, I’ll just discuss spatial dissolving to keep it simple and focused. Figure 1 shows the original GADM 3.6 vector layer. I want to dissolve all the countries to create a new spatially dissolved layer.
+
+
+
+The first step is to add a new integer column named `dissolve`.
+
+```bash
+v.db.addcolumn map=gadm36 column='dissolve int'
+```
+
+Now, we can dissolve all the areas into one category.
+
+```bash
+v.db.update map=gadm36 column=dissolve value=1
+v.dissolve input=gadm36 column=dissolve output=gadm36_dissolved
+```
+
+Drop the dummy dissolve column.
+
+```bash
+v.db.dropcolumn map=gadm36 column=dissolve
+```
+
+Is it this easy? Not yet because now all the areas in the dissolved layer have only one category and there are no ways to delete small islands because we cannot select them by category. Figure 2 shows the dissolved layer, but it only has one category for all the areas.
+
+
+
+To address this problem, we just need to recreate categories for all the features. First, delete the single category from them.
+
+```bash
+v.category input=gadm36_dissolved output=gadm36_dissolved_nocats option=del
+```
+
+Then, add unique categories to them.
+
+```bash
+v.category input=gadm36_dissolved_nocats output=gadm36_dissolved_cats option=add
+```
+
+Figure 3 shows the final categorized layer.
+
+
diff --git a/content/tutorials/howtos/fill-no-data.qmd b/content/tutorials/howtos/fill-no-data.qmd
new file mode 100644
index 0000000..c5e9cdd
--- /dev/null
+++ b/content/tutorials/howtos/fill-no-data.qmd
@@ -0,0 +1,95 @@
+---
+title: "How to fill no data cells in a raster map in GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: []
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+I need to fill onshore NULL (no data) cells in the DEM raster map shown below.
+
+
+The first step is to fill no data cells by interpolation.
+
+```bash
+g.region rast=dem1km
+r.fillnulls input=dem1km output=dem1km_nullfilled method=bilinear
+```
+
+However, [r.fillnulls](https://grass.osgeo.org/grass-stable/manuals/r.fillnulls.html) fills not only small islands of NULL cells, but also any NULL cells outside the valid raster boundaries. The output from [r.fillnulls](https://grass.osgeo.org/grass-stable/manuals/r.fillnulls.html) will be rectangular regardless of the original shape of the input raster map.
+
+
+
+I need to create a mask that only covers the original data extent, but it should also include no data areas. First, create a mask using the original DEM raster map.
+
+```bash
+r.mapcalc expression='dem1km_mask=if(!isnull(dem1km),1,null())'
+```
+
+
+
+Convert this raster mask to vector.
+
+```bash
+r.to.vect input=dem1km_mask output=dem1km_mask type=area
+```
+
+
+
+There are island areas where no data cells are. These areas are not assigned a centroid, so let’s assign a centroid to them.
+
+```bash
+v.centroids input=dem1km_mask output=dem1km_mask_nullfilled
+```
+
+
+
+Create a new column “dissolve” and assign the same value of 1 to this column for all areas. Then, dissolve all areas by this column.
+
+```bash
+v.db.addcolumn map=dem1km_mask_nullfilled column='dissolve int'
+v.db.update map=dem1km_mask_nullfilled column=dissolve value=1
+v.dissolve input=dem1km_mask_nullfilled output=dem1km_mask_dissolved column=dissolve
+```
+
+
+
+Convert the dissolved vector to raster.
+
+```bash
+v.to.rast input=dem1km_mask_dissolved output=dem1km_mask_dissolved use=val
+```
+
+The above steps failed once for me, but that’s OK. We can directly convert the undissolved vector mask to raster. This method can even be better and faster if we don’t need a dissolved vector mask.
+
+```bash
+v.to.rast input=dem1km_mask_nullfilled output=dem1km_mask_dissolved use=value
+# optionally, you can create a vector mask if you need one
+r.to.vect input=dem1km_mask_dissolved out=dem1km_mask_dissolved type=area
+```
+
+
+
+Extract the filled DEM by the final mask.
+
+```bash
+r.mapcalc expression='dem1km_clean=if(!isnull(dem1km_mask_dissolved),dem1km_nullfilled,null())'
+```
+
+
diff --git a/content/tutorials/howtos/find-ridges.qmd b/content/tutorials/howtos/find-ridges.qmd
new file mode 100644
index 0000000..f47cc0b
--- /dev/null
+++ b/content/tutorials/howtos/find-ridges.qmd
@@ -0,0 +1,48 @@
+---
+title: "How to find ridges from a drainage direction map in GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [bash, beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+Sometimes, we need to figure out where rain drops start flowing (i.e., ridges, hilltops, or headwaters with no contributing upstream cells) from a drainage direction map (`drain_dir`) only.
+
+```bash
+r.mapcalc expression='ridges=if(drain_dir[-1,-1]!=7 && drain_dir[-1,0]!=6 && drain_dir[-1,1]!=5 && drain_dir[0,1]!=4 &&'\
+' drain_dir[1,1]!=3 && drain_dir[1,0]!=2 && drain_dir[1,-1]!=1 && drain_dir[0,-1]!=8, 1, null())'
+```
+
+If we have a flow accumulation map (`flow_accum`) that was generated using a single flow direction algorithm, the following command will generate the same output because ridge cells always get assigned 1 (itself) as the number of contributing cells.
+
+```bash
+r.mapcalc expression='ridges=if(flow_accum==1, 1, null())'
+```
+
+However, when we use a multiple flow direction algorithm, there is no guarantee that ridge cells will get assign 1 because there can be partial flows from neighbor cells. In this case, the first method may be useful.
+
+Figure 1 shows ridge cells in yellow and sub-watershed polygons in the background.
+
+
+**Figure 1**: Identified ridges. The yellow cells represent ridges and the background polygons display sub-watersheds.
+
+Figure 2 clearly shows that these ridge cells do not have any contributing upstream cells.
+
+
+**Figure 2**: Close up. The red arrows show drain_dir directions.
diff --git a/content/tutorials/howtos/images/45degree.svg b/content/tutorials/howtos/images/45degree.svg
new file mode 100644
index 0000000..b2b1f5c
--- /dev/null
+++ b/content/tutorials/howtos/images/45degree.svg
@@ -0,0 +1,82 @@
+
+
diff --git a/content/tutorials/howtos/images/arcgis-fdr.svg b/content/tutorials/howtos/images/arcgis-fdr.svg
new file mode 100644
index 0000000..ebba7ce
--- /dev/null
+++ b/content/tutorials/howtos/images/arcgis-fdr.svg
@@ -0,0 +1,81 @@
+
+
diff --git a/content/tutorials/howtos/images/degree.svg b/content/tutorials/howtos/images/degree.svg
new file mode 100644
index 0000000..bdce8d0
--- /dev/null
+++ b/content/tutorials/howtos/images/degree.svg
@@ -0,0 +1,97 @@
+
+
diff --git a/content/tutorials/howtos/images/dem1km-rast.webp b/content/tutorials/howtos/images/dem1km-rast.webp
new file mode 100644
index 0000000..f967457
Binary files /dev/null and b/content/tutorials/howtos/images/dem1km-rast.webp differ
diff --git a/content/tutorials/howtos/images/dem1km_clean-rast.webp b/content/tutorials/howtos/images/dem1km_clean-rast.webp
new file mode 100644
index 0000000..f16ea0d
Binary files /dev/null and b/content/tutorials/howtos/images/dem1km_clean-rast.webp differ
diff --git a/content/tutorials/howtos/images/dem1km_mask-rast.webp b/content/tutorials/howtos/images/dem1km_mask-rast.webp
new file mode 100644
index 0000000..5a28a89
Binary files /dev/null and b/content/tutorials/howtos/images/dem1km_mask-rast.webp differ
diff --git a/content/tutorials/howtos/images/dem1km_mask-vect.webp b/content/tutorials/howtos/images/dem1km_mask-vect.webp
new file mode 100644
index 0000000..8981111
Binary files /dev/null and b/content/tutorials/howtos/images/dem1km_mask-vect.webp differ
diff --git a/content/tutorials/howtos/images/dem1km_mask_dissolved-rast.webp b/content/tutorials/howtos/images/dem1km_mask_dissolved-rast.webp
new file mode 100644
index 0000000..bc58993
Binary files /dev/null and b/content/tutorials/howtos/images/dem1km_mask_dissolved-rast.webp differ
diff --git a/content/tutorials/howtos/images/dem1km_mask_dissolved-vect.webp b/content/tutorials/howtos/images/dem1km_mask_dissolved-vect.webp
new file mode 100644
index 0000000..23435a1
Binary files /dev/null and b/content/tutorials/howtos/images/dem1km_mask_dissolved-vect.webp differ
diff --git a/content/tutorials/howtos/images/dem1km_mask_nullfilled-vect.webp b/content/tutorials/howtos/images/dem1km_mask_nullfilled-vect.webp
new file mode 100644
index 0000000..18f9f22
Binary files /dev/null and b/content/tutorials/howtos/images/dem1km_mask_nullfilled-vect.webp differ
diff --git a/content/tutorials/howtos/images/dem1km_nullfilled-rast.webp b/content/tutorials/howtos/images/dem1km_nullfilled-rast.webp
new file mode 100644
index 0000000..bf4e885
Binary files /dev/null and b/content/tutorials/howtos/images/dem1km_nullfilled-rast.webp differ
diff --git a/content/tutorials/howtos/images/fig-ga-ssd-elapsed-vs-subarea-archydropro.webp b/content/tutorials/howtos/images/fig-ga-ssd-elapsed-vs-subarea-archydropro.webp
new file mode 100644
index 0000000..2dfd573
Binary files /dev/null and b/content/tutorials/howtos/images/fig-ga-ssd-elapsed-vs-subarea-archydropro.webp differ
diff --git a/content/tutorials/howtos/images/gadm36.webp b/content/tutorials/howtos/images/gadm36.webp
new file mode 100644
index 0000000..591e465
Binary files /dev/null and b/content/tutorials/howtos/images/gadm36.webp differ
diff --git a/content/tutorials/howtos/images/gadm36_dissolved.webp b/content/tutorials/howtos/images/gadm36_dissolved.webp
new file mode 100644
index 0000000..d15164f
Binary files /dev/null and b/content/tutorials/howtos/images/gadm36_dissolved.webp differ
diff --git a/content/tutorials/howtos/images/gadm36_dissolved_cats.webp b/content/tutorials/howtos/images/gadm36_dissolved_cats.webp
new file mode 100644
index 0000000..d6fca7d
Binary files /dev/null and b/content/tutorials/howtos/images/gadm36_dissolved_cats.webp differ
diff --git a/content/tutorials/howtos/images/grass-fdr.svg b/content/tutorials/howtos/images/grass-fdr.svg
new file mode 100644
index 0000000..b2b1f5c
--- /dev/null
+++ b/content/tutorials/howtos/images/grass-fdr.svg
@@ -0,0 +1,82 @@
+
+
diff --git a/content/tutorials/howtos/images/power2.svg b/content/tutorials/howtos/images/power2.svg
new file mode 100644
index 0000000..ebba7ce
--- /dev/null
+++ b/content/tutorials/howtos/images/power2.svg
@@ -0,0 +1,81 @@
+
+
diff --git a/content/tutorials/howtos/images/r.stream.extract.webp b/content/tutorials/howtos/images/r.stream.extract.webp
new file mode 100644
index 0000000..2117449
Binary files /dev/null and b/content/tutorials/howtos/images/r.stream.extract.webp differ
diff --git a/content/tutorials/howtos/images/r.watershed.webp b/content/tutorials/howtos/images/r.watershed.webp
new file mode 100644
index 0000000..9d29016
Binary files /dev/null and b/content/tutorials/howtos/images/r.watershed.webp differ
diff --git a/content/tutorials/howtos/images/r_lfp_nc_example_single.webp b/content/tutorials/howtos/images/r_lfp_nc_example_single.webp
new file mode 100644
index 0000000..cd77be3
Binary files /dev/null and b/content/tutorials/howtos/images/r_lfp_nc_example_single.webp differ
diff --git a/content/tutorials/howtos/images/r_lfp_nc_example_single_warning.webp b/content/tutorials/howtos/images/r_lfp_nc_example_single_warning.webp
new file mode 100644
index 0000000..b5be029
Binary files /dev/null and b/content/tutorials/howtos/images/r_lfp_nc_example_single_warning.webp differ
diff --git a/content/tutorials/howtos/images/ridges-closeup.webp b/content/tutorials/howtos/images/ridges-closeup.webp
new file mode 100644
index 0000000..9553047
Binary files /dev/null and b/content/tutorials/howtos/images/ridges-closeup.webp differ
diff --git a/content/tutorials/howtos/images/ridges.webp b/content/tutorials/howtos/images/ridges.webp
new file mode 100644
index 0000000..84e2065
Binary files /dev/null and b/content/tutorials/howtos/images/ridges.webp differ
diff --git a/content/tutorials/howtos/images/taudem-fdr.svg b/content/tutorials/howtos/images/taudem-fdr.svg
new file mode 100644
index 0000000..55f5840
--- /dev/null
+++ b/content/tutorials/howtos/images/taudem-fdr.svg
@@ -0,0 +1,82 @@
+
+
diff --git a/content/tutorials/howtos/images/taudem.svg b/content/tutorials/howtos/images/taudem.svg
new file mode 100644
index 0000000..55f5840
--- /dev/null
+++ b/content/tutorials/howtos/images/taudem.svg
@@ -0,0 +1,82 @@
+
+
diff --git a/content/tutorials/howtos/import-arcgis-flowdir.qmd b/content/tutorials/howtos/import-arcgis-flowdir.qmd
new file mode 100644
index 0000000..a4ea114
--- /dev/null
+++ b/content/tutorials/howtos/import-arcgis-flowdir.qmd
@@ -0,0 +1,68 @@
+---
+title: "How to import ArcGIS flow direction into GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [bash, beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+As shown in Figure 1, ArcGIS uses powers of two to represent the flow direction starting with 2⁰ from East in the clockwise direction. Figure 2 shows how the [`r.watershed`](https://grass.osgeo.org/grass-stable/manuals/r.watershed.html) module in GRASS encodes similar information in a raster map. This module measures the aspect of each cell in the counter-clockwise direction from East and divides the aspect angle by 45 so that East becomes 8, which is 8×45°=360°. East is not 0 because this module uses 0 to indicate depression areas.
+
+::: columns
+:::: {.column withd="50%"}
+
+**Figure 1**: ArcGIS flow direction encoding
+::::
+:::: {.column withd="50%"}
+
+**Figure 2**: GRASS drainage encoding
+::::
+:::
+
+Using this information, you can import the flow direction raster from ArcGIS into GRASS and convert it to the drainage raster.
+
+1. In ArcGIS, right click on the flow direction layer (fdr) → Data → Export Data
+2. Export fdr as GeoTiff (TIFF format). Output filename `fdr.tif` in this example.
+3. In GRASS, import `fdr.tif` first:
+ ```bash
+ r.in.gdal input=fdr.tif output=fdr
+ ```
+4. Convert fdr to drain:
+
+ ```bash
+ r.mapcalc expression="drain=int(8-log(fdr,2))"
+ ```
+
+Now, converting drain back to fdr should be straightfoward:
+
+```bash
+r.mapcalc expression="fdr2=2^(8-drain)"
+```
+
+However, this conversion is only for those drain rasters that were converted from ArcGIS flow direction maps (positive cells only). Since [`r.watershed`](https://grass.osgeo.org/grass-stable/manuals/r.watershed.html) uses negative integers to flag cells that receive flows from outside the computational extent by default, the above expression would have to be rewritten as follows if you want to handle both ArcGIS-derived drain and [`r.watershed`](https://grass.osgeo.org/grass-stable/manuals/r.watershed.html) generated drain rasters:
+
+```bash
+r.mapcalc expression="fdr2=2^(8-abs(drain))"
+```
+
+You can export `fdr2` to `fdr2.tif` using [`r.out.gdal`](https://grass.osgeo.org/grass-stable/manuals/r.out.gdal.html):
+
+```bash
+r.out.gdal input=fdr2 output=fdr2.tif
+```
diff --git a/content/tutorials/howtos/import-taudem-flowdir.qmd b/content/tutorials/howtos/import-taudem-flowdir.qmd
new file mode 100644
index 0000000..59ec5b3
--- /dev/null
+++ b/content/tutorials/howtos/import-taudem-flowdir.qmd
@@ -0,0 +1,45 @@
+---
+title: "How to import TauDEM D8 direction into GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [bash, beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+TauDEM D8 direction (`d8`) uses the following encoding:
+
+
+**Figure 1**: TauDEM D8 encoding
+
+This is GRASS drainage (`drain`) encoding:
+
+
+**Figure 2**: GRASS drainage encoding
+
+```bash
+# import D8
+r.in.gdal input=d8.tif output=d8
+
+# convert D8 to drain
+r.mapcalc ex="drain=if(d8-1,d8-1,8)"
+
+# convert drain back to D8
+# use abs() for possibly negative directions from outside the DEM
+r.mapcalc ex="d82=1+abs(drain)%8"
+```
diff --git a/content/tutorials/howtos/know-map-type.qmd b/content/tutorials/howtos/know-map-type.qmd
new file mode 100644
index 0000000..bb184c2
--- /dev/null
+++ b/content/tutorials/howtos/know-map-type.qmd
@@ -0,0 +1,83 @@
+---
+title: "How to know the map type of a GRASS archive file"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [bash, beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+GRASS provides the [`r.pack`](https://grass.osgeo.org/grass-stable/manuals/r.pack.html) and [`v.pack`](https://grass.osgeo.org/grass-stable/manuals/v.pack.html) modules that we can use to export raster and vector maps, respectively, to archive files in the .pack extension. However, unless we specify the map type explicitly in the output parameter, there is no way to know which type an archive file is for without trying [`r.pack`](https://grass.osgeo.org/grass-stable/manuals/r.pack.html) or [`v.pack`](https://grass.osgeo.org/grass-stable/manuals/v.pack.html) first. These modules will tell us which type the archive file is for. What if we don’t have access to GRASS for whatever reason?
+
+Pack files are actually `.tar.gz` files, so we can use `tar` to look into the list of contents in a pack file without uncompressing it.
+
+```bash
+> tar -tf map.pack # I know it's a map, but is it raster or vector?
+map/cidx
+map/coor
+map/dbln
+map/head
+map/hist
+map/sidx
+map/topo
+db.sqlite
+PROJ_INFO
+PROJ_UNITS
+PROJ_EPSG
+```
+
+The above pack file is a vector map because there is `db.sqlite`, but checking for `db.sqlite` only works when a map is linked to a database. We could use `map/dbln` because this metadata file always exists, but we have to pass the map name + `/dbln` to `tar` like this:
+
+```bash
+> tar -tf map.pack map/dbln # exit code 0 if vector, exit code 2 if raster
+```
+
+Let’s take a look at a raster pack file.
+
+```bash
+> tar -tf map.pack
+map/PROJ_EPSG
+map/PROJ_INFO
+map/PROJ_UNITS
+map/cats
+map/cell
+map/cell_misc/
+map/cell_misc/nullcmpr
+map/cell_misc/range
+map/cell_misc/stats
+map/cellhd
+map/colr
+map/hist
+```
+
+Unlike the contents of vector pack files, those of raster pack files are all contained inside a root directory. We can use this fact to simplify our check.
+
+```bash
+> tar -tf map.pack PROJ_INFO > /dev/null 2>&1 # keep it quiet
+> echo $? # exit code 0 if vector, exit code 2 if raster
+```
+
+In a shell script,
+
+```bash
+if tar -tf map.pack PROJ_INFO > /dev/null 2>&1; then
+ echo vector
+else
+ echo raster
+fi
+```
diff --git a/content/tutorials/howtos/longest-flow-path.qmd b/content/tutorials/howtos/longest-flow-path.qmd
new file mode 100644
index 0000000..83667be
--- /dev/null
+++ b/content/tutorials/howtos/longest-flow-path.qmd
@@ -0,0 +1,124 @@
+---
+title: "How to calculate the longest flow path in GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [bash, beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+
+
+# 1. [`r.accumulate`](https://grass.osgeo.org/grass-stable/manuals/addons/r.accumulate.html) addon
+The [`r.accumulate`](https://grass.osgeo.org/grass-stable/manuals/addons/r.accumulate.html) module implements recursive and iterative algorithms for calculating the longest flow path based on Cho (2020). This module outperforms Arc Hydro’s Longest Flow Path tool.
+
+
+
+
+
+# 2. Deprecated Raster Approach
+The [r.stream.distance](https://grass.osgeo.org/grass-stable/manuals/addons/r.stream.distance.html) module can calculate the flow length downstream (FLDS) and flow length upstream (FLUS) raster maps. In the FLDS map, each cell has the flow length from that cell to the downstream-most outlet cell flowing outside the watershed. Outlet cells along the watershed boundary get assigned 0. Similarly, in the FLUS map, each cell has the flow length from that cell to the upstream-most cell where the watershed divide starts. Those cells along the watershed divide get assigned 0. These two raster maps have the same maximum value at the outlet cell in FLUS and the upstream-most cell on the longest flow path (LFP) in FLDS. If we add both maps, cells on the output map will have the same maximum value greater than non-LFP cells and we use that fact to calculate LFP.
+
+This tutorial uses the [North Carolina sample data set](https://grass.osgeo.org/download/data/).
+
+
+## 2.1 Calculate the Longest Flow Path Raster Map
+First, create the drainage direction map (`drain_directions`) from the elevation map (`elevation`).
+
+```bash
+g.region rast=elevation
+r.watershed elevation=elevation drainage=drain_directions
+```
+Create the basin map (`basin`) at x=642455 and y=222614 and apply it as mask.
+
+```bash
+r.water.outlet input=drain_directions output=basin coordinates=642455,222614
+g.region rast=basin
+r.mask raster=basin
+```
+Create a raster map (`outlet`) containing the outlet point.
+```bash
+echo 642455,222614 | v.in.ascii input=- output=outlet separator=,
+v.to.rast input=outlet output=outlet use=cat
+```
+Calculate FLDS (`flds`) and FLUS (`flus`).
+```bash
+r.stream.distance -o stream_rast=outlet direction=drain_directions method=downstream distance=flds
+r.stream.distance -o stream_rast=outlet direction=drain_directions method=upstream distance=flus
+```
+Combine FLDS and FLUS to create a new raster map (`fldsus`).
+```bash
+r.mapcalc expression="fldsus=flds+flus"
+```
+Find the maximum flow length that each cell on FLDSUS got assigned.
+```bash
+r.info -r map=fldsus | sed '/^max=/!d; s/^max=//'
+```
+Create the LFP raster map (lfp) allowing small floating-point errors in FLDSUS. The maximum cell value obtained from the last step (or LFP distance) was 21452.825. Subtract a small number to avoid a potential floating-point error.
+```bash
+r.mapcalc expression="lfp=if(fldsus>=21452.825-0.0005,1,null())"
+```
+
+The `lfp` map is the longest flow path raster map.
+
+## 2.2 Calculate the Longest Flow Path Vector Map
+
+Simply converting the lfp raster map to vector won’t produce the correct vector map because of [this issue](https://idea.isnew.info/how-to-delineate-stream-networks-in-grass-gis.html#-how-not-to-delineate-stream-networks). To properly trace the longest flow path in vector format, we need to find the headwater raster cells and start tracing flow directions from there. Using the maximum flow length found above (21452.825), find the headwater cells from FLDS and create a new vector map with the headwater points.
+
+```bash
+r.mapcalc expression="heads=if(flds>=21452.825-0.0005,1,null())"
+r.to.vect input=heads output=heads type=point
+```
+Trace flow directions from the headwater points to calculate the longest flow path in vector format.
+```bash
+r.path input=drain_directions vector_path=path start_points=heads
+```
+The output vector map (path) includes the longest flow path, but it passes through the outlet and flows beyond the watershed. We need to split the path at the outlet first and select only the upstream segment within the watershed. To split the path at the outlet, create a new vector map with the snapped outlet point.
+
+```bash
+r.to.vect input=outlet output=outlet type=point
+```
+Find the coordinates of the snapped outlet. Read snapped x=642455 and y=222615 and split the path at this point.
+
+```bash
+v.to.db -p map=outlet option=coor
+v.edit map=path tool=break coords=642455,222615
+```
+Select the upstream segment only that touches the headwater points.
+
+```bash
+v.select ainput=path binput=heads output=lfp
+```
+Finally, we got the longest flow path in `lfp`.
+
+
+
+This example produces two lines with the same flow length.
+
+
+
+## 2.3 [`r.lfp`](https://grass.osgeo.org/grass72/manuals/addons/r.lfp.html) Addon
+The procedure above has been implemented as a GRASS addon module [`r.lfp`](https://grass.osgeo.org/grass72/manuals/addons/r.lfp.html).
+
+```bash
+g.extension extension=r.lfp
+r.lfp input=elevation output=lfp coordinates=642455,222614
+```
+
+# 3. References
+- Cho, H. (2020). A Recursive Algorithm for Calculating the Longest Flow Path and Its Iterative Implementation. *Environmental Modelling & Software*, 131, 104774. doi:10.1016/j.envsoft.2020.104774
diff --git a/content/tutorials/howtos/patch-multiple-polygons.qmd b/content/tutorials/howtos/patch-multiple-polygons.qmd
new file mode 100644
index 0000000..f8b0472
--- /dev/null
+++ b/content/tutorials/howtos/patch-multiple-polygons.qmd
@@ -0,0 +1,53 @@
+---
+title: "How to patch multiple polygon vector maps cleanly in GRASS"
+author: "Huidae Cho"
+date: 2022-03-25
+date-modified: today
+format:
+ html:
+ toc: true
+ toc-depth: 2
+ code-tools: true
+ code-copy: true
+ code-fold: false
+ html-math-method: katex
+ theme:
+ - cosmo
+categories: [bash, beginner]
+linkcolor: green
+urlcolor: green
+citecolor: green
+highlight-style: github
+# engine: knitr
+execute:
+ eval: false
+---
+The [`v.patch`](https://grass.osgeo.org/grass-stable/manuals/v.patch.html) module does an excellent job patching multiple polygon vector maps, but it sometimes leaves areas without centroids. Here, I’m sharing what I do to clean the topology of [`v.patch`](https://grass.osgeo.org/grass-stable/manuals/v.patch.html) outputs. Let’s say I want to patch 5 subbasin polygon vector maps from `subbasin1,...,subbasin5`. I start patching them into one vector map.
+
+```bash
+v.patch input=subbasin1,subbasin2,subbasin3,subbasin4,subbasin5 output=subbasins
+```
+
+If I’m lucky, [`v.info`](https://grass.osgeo.org/grass-stable/manuals/v.info.html) should report the same number of areas and centroids. If it’s not the case, you need to clean the output vector map. First, break boundaries at intersections, remove duplicate features, small angles, and duplicate area centroids.
+
+```bash
+v.clean input=subbasins output=tmp tool=break,rmdupl,rmsa,rmdac
+```
+
+Add missing categories to areas. After running this command, there should be the same number of areas and centroids.
+
+```bash
+v.category input=tmp output=tmp2 option=add type=area
+```
+
+However, at this point, there are duplicate categories, so recategorize all centroids. Remove existing categories first.
+
+```bash
+v.category input=tmp2 output=tmp3 option=del cat=-1
+```
+
+Restart from 1 and save the output as the final vector map.
+
+```bash
+v.category input=tmp3 output=subbasins option=add
+```