{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "

Notebook for A Vector Approach to Drainage Network Analysis Based on LiDAR Data

\n", "

\n", "$Fangzheng$ $Lyu^{1,2}$, $Zewei$ $Xu^{1,2}$, $Xinlin$ $Ma^{3}$, $Shaohua$ $Wang^{1,2}$, $Zhiyu$ $Li^{1,2}$, $Shaowen$ $Wang^{1,2}$\n", "

\n", "

\n", "$^{1}$$Department$ $of$ $Geography$ $and$ $Geographic$ $Information$ $Science$, $University$ $of$ $Illinois$ $at$ $Urbana-Champaign$, $Urbana$, $IL$, $USA$
\n", "$^{2}$$CyberGIS$ $Center$ $for$ $Advanced$ $Digital$ $and$ $Spatial$ $Studies$, $University$ $of$ $Illinois$ $at$ $Urbana-Champaign$, $Urbana$, $IL$, $USA$
\n", "$^{3}$$Department$ $of$ $Management$ $and$ $Urban$ $Planning$, $Tsinghua$ $University$, $Beijing$, $China$
\n", "\n", "$Corresponding$ $Author:$ $shaowen@illinois.edu$\n", "

\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Drainage network analysis is fundamental to understanding the characteristics of surface hydrology. Based on elevation data, drainage network analysis is often used to extract key hydrological features like drainage networks and streamlines. Limited by raster-based data models, conventional drainage network algorithms typically allow water to flow in 4 or 8 directions (surrounding grids) from a raster grid. To resolve this limitation, this paper describes a new vector-based method for drainage network analysis that allows water to flow in any direction around each location. The method is enabled by rapid advances in Light Detection and Ranging (LiDAR) remote sensing and high-performance computing. The drainage network analysis is conducted using a high-density point cloud instead of Digital Elevation Models (DEMs) at coarse resolutions. Our computational experiments show that the vector-based method can better capture water flows without limiting the number of directions due to imprecise DEMs. Our case study applies the method to Rowan County watershed, North Carolina in the US. After comparing the drainage networks and streamlines detected with corresponding reference data from US Geological Survey generated from the Geonet software, we find that the new method performs well in capturing the characteristics of water flows on landscape surfaces in order to form an accurate drainage network." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook is a sample notebook for running a small size dataset (from the LiDAR dataset of Rowan Watershed) with our new method for drainage system analysis. The algorithm is implemented with an execuation sample dataset." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Paper:\n", "\n", "Fangzheng Lyu, Zewei Xu, Xinlin Ma, Shaohua Wang, Zhiyu Li, Shaowen Wang, A vector-based method for drainage network analysis based on LiDAR data, Computers & Geosciences, Volume 156, 2021, 104892, ISSN 0098-3004\n", "\n", "https://www.sciencedirect.com/science/article/pii/S0098300421001849\n", "\n", "Github repo:\n", "\n", "https://github.com/cybergis/Drainage-System-Analysis-Research" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Table of Contents\n", "* [Psudocode](#psudocode)\n", "* [Set up](#chapter1)\n", " * [Import Library](#section_1_1)\n", " * [Set up input variables](#section_1_2)\n", " * [Read LiDAR data using laspy](#section_1_3)\n", "* [Create a hash table](#chapter2)\n", "* [Elevation Function](#chapter3)\n", "* [Simulate the water flow](#chapter4)\n", " * [Track the flow direction](#section_4_1)\n", "* [Visulization](#chapter5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Psudocode " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Set up \n", "The Library used for in the algorithm is set up here" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Set Up Environment and Import Library " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%load_ext pycodestyle_magic\n", "#%pycodestyle_on\n", "%flake8_on --ignore E501,E251,E231,E225" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import library" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import os\n", "import datetime\n", "import laspy\n", "import numpy as np\n", "import pandas as pd\n", "from numpy.linalg import inv\n", "import math\n", "import sys" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Set up input variables " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# var1 is the starting location in the x-axis\n", "var1 = 5\n", "# var2 is the starting location in the y-axis\n", "var2 = 5\n", "# var3 is the angle different we want\n", "var3 = 30" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Prepare sample LiDAR data " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Missing sample data!\n", "Copying sample data from shared folder...\n" ] } ], "source": [ "import os\n", "from shutil import copyfile\n", "import gdown\n", "\n", "# download sample data if it is not local\n", "local_sample_path = \"./3_2.las\"\n", "if not os.path.isfile(local_sample_path):\n", " print(\"Missing sample data!\")\n", " sample_shared = \"/home/jovyan/shared_data/data/drainage_system_analysis_research/3_2.las\"\n", " if os.path.join(sample_shared):\n", " print(\"Copying sample data from shared folder...\")\n", " copyfile(sample_shared, local_sample_path)\n", " else:\n", " print(\"Copying sample data from shared google drive...\")\n", " url_gdrive = 'https://drive.google.com/uc?id=1JOl1IylIZg72QdMM89xs10NLFk4rObml'\n", " gdown.download(url_gdrive, local_sample_path, quiet=False)\n", "else:\n", " print(\"Sample data already exists\")\n", "if not os.path.isfile(local_sample_path):\n", " print(\"Can not retrieve sample data!\")" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-rw-r--r-- 1 jovyan users 159M Nov 16 16:50 ./3_2.las\r\n" ] } ], "source": [ "!ls {local_sample_path} -alh" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Read LiDAR data using laspy " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "# Store the LiDAR data as infile\n", "infile = laspy.file.File(local_sample_path, mode=\"r\")\n", "# Get the value for x axis, y axis, and elevation for LiDAR point cloud\n", "ground_x = infile.x\n", "ground_y = infile.y\n", "ground_z = infile.z\n", "# Normalize the output for the LiDAR file\n", "ground_x_2 = ground_x-ground_x.min()\n", "ground_y_2 = ground_y-ground_y.min()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The total amount of LiDAR point in the LiDAR file" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5546582" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(ground_z)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The x, y, z value for points in the LiDAR file are stored" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "x = ground_x_2\n", "y = ground_y_2\n", "z = ground_z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use a dictionary to store the datset" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "threedarray = np.vstack((x,y,z)).T\n", "dictionary = pd.Series(threedarray.tolist(), index=map(lambda a: round(a,2), x.tolist()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An example of the format of the dataset" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.11 [1.1100000001024455, 238.38000000000466, 690.64]\n", "1.11 [1.1100000001024455, 357.63000000000466, 691.02]\n", "1.11 [1.1100000001024455, 493.71999999997206, 695.84]\n", "1.11 [1.1100000001024455, 510.03000000002794, 695.94]\n", "1.11 [1.1100000001024455, 543.7900000000373, 694.55...\n", "1.11 [1.1100000001024455, 608.5899999999674, 694.42]\n", "1.11 [1.1100000001024455, 668.1500000000233, 698.37]\n", "1.11 [1.1100000001024455, 804.5600000000559, 698.38]\n", "1.11 [1.1100000001024455, 803.4599999999627, 698.38]\n", "1.11 [1.1100000001024455, 795.9000000000233, 698.37]\n", "1.11 [1.1100000001024455, 829.4000000000233, 698.32]\n", "1.11 [1.1100000001024455, 843.5400000000373, 697.71]\n", "1.11 [1.1100000001024455, 850.109999999986, 697.59]\n", "1.11 [1.1100000001024455, 899.75, 699.48]\n", "1.11 [1.1100000001024455, 927.9899999999907, 699.64]\n", "1.11 [1.1100000001024455, 1039.5100000000093, 699.7]\n", "1.11 [1.1100000001024455, 1207.2900000000373, 703.09]\n", "1.11 [1.1100000001024455, 1221.25, 704.29]\n", "1.11 [1.1100000001024455, 1261.1600000000326, 707.38]\n", "1.11 [1.1100000001024455, 1298.140000000014, 707.98]\n", "1.11 [1.1100000001024455, 1284.0899999999674, 707.6]\n", "1.11 [1.1100000001024455, 1332.920000000042, 708.77]\n", "1.11 [1.1100000001024455, 1338.140000000014, 708.79]\n", "1.11 [1.1100000001024455, 1457.170000000042, 711.67]\n", "1.11 [1.1100000001024455, 1442.109999999986, 711.32]\n", "1.11 [1.1100000001024455, 1463.6600000000326, 711.9]\n", "1.11 [1.1100000001024455, 1756.0500000000466, 712.6...\n", "dtype: object" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dictionary[1.11]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create a hash table data structure to store the LiDAR data " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create the index for each LiDAR data point" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "list_a = map(lambda a: 10000*int(a), x.tolist())\n", "list_b = map(lambda a: int(a), y.tolist())\n", "index_list = [sum(x) for x in zip(list_a, list_b)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Generate the hash table and use dictionary data structure to store the dataset" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "grid_dictionary = pd.Series(threedarray.tolist(), index=index_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Get a overview of the dataset" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "16 [0.02000000001862645, 16.46999999997206, 688.92]\n", "6 [0.0, 6.800000000046566, 688.9300000000001]\n", "28 [0.0, 28.400000000023283, 688.96]\n", "280008 [28.010000000009313, 8.900000000023283, 689.47]\n", "60002 [6.370000000111759, 2.2800000000279397, 689.0]\n", " ... \n", "352542 [35.6500000001397, 2542.0200000000186, 702.96]\n", "322542 [32.199999999953434, 2542.030000000028, 702.85]\n", "372542 [37.40999999991618, 2542.930000000051, 703.2]\n", "372547 [37.75, 2547.359999999986, 703.51]\n", "482542 [48.47999999998137, 2542.0200000000186, 704.2]\n", "Length: 5546582, dtype: object" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "grid_dictionary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Function to find the elevation " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the function used in the algorithm to find the elevation of any given point (x, y) using bilinear interpolation." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "def find_elevation_new(x, y):\n", " # bilinear interpolation\n", " storage = []\n", " # curr_index = 10000*int(x)+int(y)\n", " diff = 0\n", " while(len(storage)<4):\n", " # Find all the data within the nearby grid\n", " temp = []\n", " # For loop here to find candidate points for bilinear interpolation\n", " for i in range(int(x-diff), int(x+diff+1)):\n", " for j in range(int(y-diff), int(y+diff+1)):\n", " if (i==int(x-diff) or i==int(x+diff) or j==int(y-diff) or j==int(y+diff)):\n", " try:\n", " rt = grid_dictionary[10000*i+j]\n", " if (type(rt)==list):\n", " temp.append(rt)\n", " else:\n", " for it in range(0,len(rt)):\n", " temp.append(rt[it])\n", " except Exception:\n", " pass\n", " temp.sort(key = lambda e: (e[0]-x)*(e[0]-x)+(e[1]-y)*(e[1]-y))\n", " if (len(storage)+len(temp) <= 4):\n", " # add them\n", " for i in range(0,len(temp)):\n", " storage.append(temp[i])\n", " else:\n", " k = 0\n", " while(len(storage) != 4):\n", " storage.append(temp[k])\n", " k = k+1\n", " diff = diff+1\n", " # find the points used for data interpolation\n", " new_storage = storage[:4]\n", " a = [[1,new_storage[0][0], new_storage[0][1], new_storage[0][0]*new_storage[0][1]],\n", " [1, new_storage[1][0], new_storage[1][1], new_storage[1][0]*new_storage[1][1]],\n", " [1, new_storage[2][0], new_storage[2][1], new_storage[2][0]*new_storage[2][1]],\n", " [1, new_storage[3][0], new_storage[3][1], new_storage[3][0]*new_storage[3][1]]]\n", " b = [new_storage[0][2], new_storage[1][2], new_storage[2][2], new_storage[3][2]]\n", " try:\n", " # Conduct bilinear interpolation\n", " coef_matrix = np.matmul(inv(a), b)\n", " rt = coef_matrix[0]+coef_matrix[1]*x+coef_matrix[2]*y+coef_matrix[3]*x*y\n", " return rt\n", " except Exception:\n", " return 10000" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simulate the water flow \n", "This is the major step in the algorithm that is used for simulation of the water flow.\n", "\n", "This cell may run up to ~15 mins." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2021-11-16 16:51:23.586189\n", "(500, 500)\n", "(500, 501)\n", "(500, 502)\n", "(500, 503)\n", "(500, 504)\n", "(500, 505)\n", "(500, 506)\n", "(500, 507)\n", "(500, 508)\n", "(500, 509)\n", "(500, 510)\n", "(501, 500)\n", "(501, 501)\n", "(501, 502)\n", "(501, 503)\n", "(501, 504)\n", "(501, 505)\n", "(501, 506)\n", "(501, 507)\n", "(501, 508)\n", "(501, 509)\n", "(501, 510)\n", "(502, 500)\n", "(502, 501)\n", "(502, 502)\n", "(502, 503)\n", "(502, 504)\n", "(502, 505)\n", "(502, 506)\n", "(502, 507)\n", "(502, 508)\n", "(502, 509)\n", "(502, 510)\n", "(503, 500)\n", "(503, 501)\n", "(503, 502)\n", "(503, 503)\n", "(503, 504)\n", "(503, 505)\n", "(503, 506)\n", "(503, 507)\n", "(503, 508)\n", "(503, 509)\n", "(503, 510)\n", "(504, 500)\n", "(504, 501)\n", "(504, 502)\n", "(504, 503)\n", "(504, 504)\n", "(504, 505)\n", "(504, 506)\n", "(504, 507)\n", "(504, 508)\n", "(504, 509)\n", "(504, 510)\n", "(505, 500)\n", "(505, 501)\n", "(505, 502)\n", "(505, 503)\n", "(505, 504)\n", "(505, 505)\n", "(505, 506)\n", "(505, 507)\n", "(505, 508)\n", "(505, 509)\n", "(505, 510)\n", "(506, 500)\n", "(506, 501)\n", "(506, 502)\n", "(506, 503)\n", "(506, 504)\n", "(506, 505)\n", "(506, 506)\n", "(506, 507)\n", "(506, 508)\n", "(506, 509)\n", "(506, 510)\n", "(507, 500)\n", "(507, 501)\n", "(507, 502)\n", "(507, 503)\n", "(507, 504)\n", "(507, 505)\n", "(507, 506)\n", "(507, 507)\n", "(507, 508)\n", "(507, 509)\n", "(507, 510)\n", "(508, 500)\n", "(508, 501)\n", "(508, 502)\n", "(508, 503)\n", "(508, 504)\n", "(508, 505)\n", "(508, 506)\n", "(508, 507)\n", "(508, 508)\n", "(508, 509)\n", "(508, 510)\n", "(509, 500)\n", "(509, 501)\n", "(509, 502)\n", "(509, 503)\n", "(509, 504)\n", "(509, 505)\n", "(509, 506)\n", "(509, 507)\n", "(509, 508)\n", "(509, 509)\n", "(509, 510)\n", "(510, 500)\n", "(510, 501)\n", "(510, 502)\n", "(510, 503)\n", "(510, 504)\n", "(510, 505)\n", "(510, 506)\n", "(510, 507)\n", "(510, 508)\n", "(510, 509)\n", "(510, 510)\n", "2021-11-16 17:07:38.035808\n" ] } ], "source": [ "# The area_length variable here represents how large the area the users want to calculate.\n", "area_length = 10\n", "\n", "# Print the current time before the execution of the algorithm\n", "print(datetime.datetime.now())\n", "min_x = var1*100\n", "min_y = var2*100\n", "max_x = var1*100+area_length\n", "max_y = var2*100+area_length\n", "increment = 1\n", "angle = var3\n", "x_coord = min_x\n", "y_coord = min_y\n", "rt = []\n", "while x_coord!=(max_x+1):\n", " while y_coord!=(max_y+1):\n", " # find the elevation of the current coordinate\n", " curr_elevation = find_elevation_new(x_coord, y_coord)\n", " curr_x = x_coord\n", " curr_y = y_coord\n", " # print(\"Starting Point:\")\n", " print((curr_x,curr_y))\n", " curr_array = []\n", " while ((curr_x>=min_x and curr_x<=max_x) and (curr_y>=min_y and curr_y<=max_y)):\n", " # print((curr_x, curr_y, curr_elevation))\n", " curr_array.append((curr_x, curr_y))\n", " rt_x = curr_x\n", " rt_y = curr_y\n", " rt_elevation = curr_elevation\n", " angel_diff = 0\n", " # find the elevation of all candidate points\n", " while(angel_diff<360):\n", " new_x = curr_x+increment*math.sin(math.pi/180*angel_diff)\n", " new_y = curr_y+increment*math.cos(math.pi/180*angel_diff)\n", " new_elevation = find_elevation_new(new_x, new_y)\n", " # print((angel_diff,new_x,new_y,new_elevation,rt_x,rt_y,rt_elevation))\n", " if (new_elevation" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "for i in range(0,len(rt)):\n", " for j in range(0,len(rt[i])):\n", " data[int(math.floor(rt[i][j][0]))-var1*100][int(math.floor(rt[i][j][1]))-var2*100]=data[int(math.floor(rt[i][j][0]))-var1*100][int(math.floor(rt[i][j][1]))-var2*100]+1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visulization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The result after tracking the water direction" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[4, 6, 8, 9, 13, 1, 2, 2, 3, 4, 2], [3, 4, 4, 14, 7, 1, 1, 2, 3, 10, 2], [2, 5, 6, 9, 14, 4, 5, 5, 9, 4, 2], [1, 2, 8, 2, 12, 1, 2, 5, 5, 2, 2], [1, 2, 6, 4, 5, 4, 4, 4, 1, 2, 1], [1, 5, 1, 3, 4, 3, 1, 4, 1, 2, 1], [5, 1, 10, 6, 3, 1, 2, 2, 1, 2, 2], [2, 1, 5, 4, 2, 9, 4, 6, 5, 2, 1], [4, 1, 4, 2, 2, 4, 2, 2, 1, 1, 1], [1, 2, 3, 1, 2, 2, 1, 1, 1, 6, 1], [1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1]]\n" ] } ], "source": [ "print(data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Simple visulization of the result, with brighter area having more water flowing through" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", "\n", "plt.imshow(data, interpolation='none')\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3-0.8.0", "language": "python", "name": "python3-0.8.0" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }