Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Counting Ohio Dams

In the previous example we validated the network using basin_area. You can also use other properties like river mile to validate the network connection. Or to validate the metadata.

In this example, we’ll be assuming there are some errors and continue with our analysis. Because the errors are mostly in the placement of dams, the count of dam upstream of a gage is still the same. For example:

If our network is:

gage1 -> dam1
dam1 -> gage2

instead of:

gage1 -> gage2
dam1 -> gage2

For both gage, number of dams upstream (or the first dam construction year upstead) is still the same, even though there is error on the same calculation in the dam.

Load Network and Attributes

First load the network

network load_file("data/ohio-river/ohio.network")
network count()
network outlet()

Results:

5987
"03399800"

Identify Dams and Gages

Since we need a quick and easy way to identify USGS gage and NID dam, we use regex to categorize them.

node.is_usgs = NAME match "^[0-9]+";
node.is_dam = !is_usgs;
network count(nodes.is_usgs)
network count(nodes.is_dam)

Results:

1806
4181

Count Dams/Gages Upstream

Now we simply count the number of dams and gages upstream recursively. We run it inputs first, so that the count begins with leaf nodes where we get 1 for the category we’re counting and 0 otherwise, then we propagate that value downstream.

node<inp>.ngage = int(is_usgs) + sum(inputs.ngage);
node<inp>.ndam = int(!is_usgs) + sum(inputs.ndam);

node(INDEX < 10) array(ngage, ndam)

Results:

{
  03399800 = [1806, 4181],
  IL50499 = [1805, 4181],
  IL00070 = [0, 1],
  IL00955 = [0, 1],
  IL40055 = [0, 1],
  IL00039 = [3, 2],
  03386500 = [3, 1],
  IL00102 = [2, 1],
  03386000 = [2, 0],
  03385500 = [1, 0]
}

If we run this code without the inputsfirst/inp propagation, we get an error because the node’s inputs will not have ngage and ndam values.

The counting is done, super simple right? NADI’s ability to work with networks mean that some things are super simple compared to other languages.

GIS Visualization

Again, if we use nadi-gis plugin, then we can export this result and visualize it in QGIS.

network gis_save_nodes(
	"output/ohio-gages-count.gpkg",
	"GEOM",
	{
		NAME="String",
		is_usgs="String",
		ndam="Integer",
		ngage="Integer"
	}
)

After applying some filters and styles in QGIS, we can look at the numbers visually,

Gage/Dam Count

Extra: Counting Large Dams

The definition of large dam: https://www.icold-cigb.org/GB/dams/definition_of_a_large_dam.asp

  • height of more than 15 meters,
  • height between 5m and 15m impounding more than 3 million cubic meters.

Converting them to imperial units we get:

MetricImperial
15m49.21ft
5m16.40ft
3 x 10⁶ m³810.71 acre feet

Now, we need to load the dam attributes from NID database.

network gis_load_attrs("data/ohio-river/nid-uniq.gpkg", "nidId")
node(is_dam).dam_height = float(nidHeight);
node(is_dam).dam_storage = float(nidStorage);

network count(nodes.is_dam)
network count(nodes.dam_height? & nodes.dam_storage?)
node(is_dam & (INDEX < 10)) array(dam_height, dam_storage)

Results:

4181
4181
{
  IL50499 = [56, 738700],
  IL00070 = [24, 153],
  IL00955 = [36, 366],
  IL40055 = [28, 47],
  IL00039 = [59, 633],
  IL00102 = [38, 1814]
}

Lot’s of basins do not have basin area.

First, using the previous requirements for large dams:

node.large_dam = is_dam & ((dam_height > 49) | ((dam_height > 16) & (dam_storage > 811)));
network count(nodes.large_dam)
network count(nodes.large_dam) / count(nodes.is_dam)

Results:

1135
0.27146615642190863

This will allow us to run the same counting as before:

node<inp>.nldam = int(large_dam) + sum(inputs.nldam);
node(INDEX < 10) array(ndam, nldam)

Results:

{
  03399800 = [4181, 1135],
  IL50499 = [4181, 1135],
  IL00070 = [1, 0],
  IL00955 = [1, 0],
  IL40055 = [1, 0],
  IL00039 = [2, 2],
  03386500 = [1, 1],
  IL00102 = [1, 1],
  03386000 = [0, 0],
  03385500 = [0, 0]
}

Although not obivious, the numbers were quite large, so when I inspected the NID data, some dams seem to have very high height values that do not match.

Let’s load basin area and flag any locations with more than 50ft of dam height, and less than 10 square miles of basin area.

node(is_dam & drainageArea?).basin_area = float(drainageArea);
network count(nodes.basin_area?)
node(! basin_area?).basin_area = nan;

node.flag = large_dam & ((dam_height > 50) & (basin_area < 10));
network count(nodes.flag)
node(flag & (INDEX < 1000)) array(dam_height, dam_storage, basin_area)

Results:

3766
353
{
  IL50593 = [108, 10790, 0.27],
  IL50678 = [110, 5250, 0.1],
  IL50066 = [55, 1087, 0.7000000000000001],
  IN00446 = [54, 7538, 4.75],
  IL00688 = [52, 3474, 3.3000000000000003],
  IN00439 = [56, 321, 0.52],
  IN03920 = [55, 39, 0.02],
  IN00505 = [56, 66, 0.03],
  IN00036 = [63, 1595, 1.2],
  IN00181 = [55, 1380, 4.7],
  IN00201 = [55, 2861, 4.69],
  IN00069 = [68, 6718, 5.8],
  IN03731 = [57, 128, 0.8200000000000001],
  IN03007 = [55, 29800, 0],
  IN00197 = [62, 3555, 1.82],
  IN00103 = [60, 1158, 0.36],
  IN00133 = [82, 13000, 9.34],
  IN00342 = [63, 198, 0.19],
  IN00565 = [53, 109, 0.1]
}

We don’t have all basin areas, but from this we flagged around 300 dams. Looking at the values, IL50678 basically says it has a dam with height of 110, but basin area of 0.1, which seems very unreasonable. To remove these from our identification process, let’s add another category.

node.large_dam = is_dam & (((dam_height > 49) | ((dam_height > 16) & (dam_storage > 811))) & (basin_area > 10));
network count(nodes.large_dam)
network count(nodes.large_dam) / count(nodes.is_dam)

Results:

321
0.07677589093518297

The number of dams that are now categorized as large dams have been reduced significantly.

Extra: Looking at Main Stem of the River

Let’s look at the values along the main stem. LEVEL == 0 means the mainstem of the network.

node(LEVEL==0) array(ngage, ndam, nldam)

Results:

{
  03399800 = [1806, 4181, 1135],
  IL50499 = [1805, 4181, 1135],
  03384500 = [1801, 4169, 1129],
  IL50443 = [1799, 4164, 1129],
  03381700 = [1785, 4065, 1097],
  KY03060 = [1454, 3087, 905],
  03322420 = [1454, 3086, 904],
  IN03661 = [1451, 3067, 897],
  03322190 = [1451, 3061, 897],
  KY01059 = [1450, 3061, 897],
  03322000 = [1448, 3044, 894],
  IN04061 = [1388, 2822, 826],
  03304300 = [1388, 2821, 826],
  KY03059 = [1387, 2821, 826],
  03303500 = [1386, 2809, 823],
  KY01255 = [1383, 2771, 815],
  KY00842 = [1383, 2769, 814],
  IN03297 = [1383, 2768, 813],
  KY03058 = [1383, 2765, 813],
  03303280 = [1383, 2764, 812],
  IN03192 = [1382, 2764, 812],
  KY01272 = [1373, 2726, 807],
  03294600 = [1322, 2576, 789],
  KY00597 = [1318, 2572, 788],
  KY01022 = [1318, 2571, 788],
  03294500 = [1318, 2570, 788],
  KY03034 = [1316, 2552, 780],
  03293600 = [1316, 2551, 779],
  03293551 = [1315, 2551, 779],
  03293550 = [1314, 2551, 779],
  03293548 = [1313, 2551, 779],
  IN00643 = [1295, 2533, 775],
  KY03033 = [1220, 2287, 689],
  03277200 = [1220, 2286, 688],
  KY01215 = [1217, 2272, 685],
  OH01690 = [1111, 2112, 657],
  03255000 = [1091, 2087, 648],
  OH01350 = [1013, 1898, 616],
  03238680 = [1011, 1886, 612],
  KY03032 = [1010, 1886, 612],
  KY00938 = [1004, 1869, 609],
  KY01093 = [1003, 1868, 609],
  03238000 = [1003, 1867, 608],
  OH03181 = [1002, 1866, 607],
  03217200 = [883, 1700, 573],
  KY03031 = [875, 1692, 572],
  03216600 = [875, 1691, 571],
  03216000 = [868, 1680, 568],
  03206000 = [788, 1596, 513],
  WV05302 = [760, 1568, 503],
  03201500 = [622, 1335, 415],
  03160000 = [621, 1335, 415],
  OH00971 = [619, 1327, 411],
  WV05312 = [617, 1318, 408],
  WV05313 = [617, 1317, 407],
  WV05301 = [617, 1314, 407],
  03159870 = [617, 1313, 406],
  WV10702 = [609, 1296, 399],
  03159530 = [609, 1295, 398],
  03151000 = [564, 1186, 358],
  OH00943 = [563, 1185, 358],
  OH00939 = [563, 1184, 358],
  03150700 = [563, 1182, 357],
  WV07301 = [396, 842, 288],
  WV10301 = [393, 836, 285],
  03114280 = [393, 835, 284],
  03114275 = [392, 835, 284],
  WV05108 = [389, 827, 280],
  03113600 = [385, 805, 273],
  03112500 = [377, 798, 270],
  03111534 = [373, 769, 251],
  WV06908 = [372, 769, 251],
  03111520 = [372, 768, 250],
  03111515 = [371, 768, 250],
  OH03169 = [368, 742, 246],
  WV02901 = [362, 704, 227],
  03110690 = [362, 703, 226],
  OH03172 = [361, 703, 226],
  03110685 = [361, 702, 226],
  03108500 = [344, 661, 219],
  PA00128 = [343, 661, 219],
  03108490 = [343, 660, 218],
  PA00127 = [279, 528, 184],
  03086000 = [279, 527, 183],
  PA00126 = [276, 519, 181],
  03085730 = [276, 518, 180],
  PA01994 = [142, 265, 96],
  PA00120 = [141, 264, 96],
  03085000 = [141, 263, 95],
  03075070 = [100, 192, 73],
  03075000 = [98, 172, 64],
  PA00122 = [97, 172, 64],
  PA00123 = [94, 156, 60],
  PA01549 = [89, 135, 50],
  PA01265 = [89, 133, 49],
  PA00124 = [88, 121, 44],
  03072655 = [88, 120, 43],
  03072500 = [86, 118, 43],
  PA00125 = [45, 90, 36],
  03063000 = [45, 89, 35],
  WV06106 = [43, 78, 31],
  03062450 = [43, 77, 30],
  03062445 = [42, 77, 30],
  WV06107 = [40, 75, 29],
  WV06108 = [40, 74, 28],
  03062224 = [40, 73, 27],
  03062000 = [37, 69, 26],
  03061000 = [12, 37, 9],
  03059000 = [9, 20, 8],
  WV03336 = [8, 20, 8],
  03058975 = [8, 18, 8],
  03058500 = [7, 16, 7],
  WV04110 = [6, 6, 1],
  03058020 = [6, 5, 1],
  WV04111 = [5, 2, 1],
  03058006 = [5, 1, 1],
  WV04114 = [4, 1, 1],
  03058000 = [4, 0, 0],
  03057900 = [3, 0, 0],
  03057300 = [1, 0, 0]
}

If you want to be more accurate, we can load the SiteName from GIS file and match the node with “Ohio River” in its name with the lowest order to find the first Ohio River Node.