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,
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:
Metric | Imperial |
---|---|
15m | 49.21ft |
5m | 16.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.