Modeling flow on terrains

(Laura Toma, may13th 2008)

Overview

In this second part of the exam you will augment the program to read and render grid terrains with the capability to compute the flow of water on the terrain.

To make things faster, I am providing the code for reading and rendering grids. All code and test files are here. You'll find a single java file, Grid.java, which contains the skeleton of the program, and two test grids. You should be able to compile and run it using one of the two test files provided:

javac Grid.java
java Grid test2.asc
java Grid set1.as
What you will see when you run the program skeleton is the elevation grid and an empty flow grid, rendered on top of it (click and move the window to see the elevation grid underneath).

The only method that you need to fill in is:

int[][] computeFlow()
This method creates a flow grid and returns it. Let's talk about flow.

Flow on a grid

The goal of flow modeling is to quantify how much water flows through any point of the terrain. In the case when the terrain is a grid, you will compute a flow grid which has the same size as the elevation grid.
int[][] flow

How does flow work? Initially every single point in the grid has 1 unit of water. So the very first thing that you want to do in computeFlow() after you create the flow grid is

for (int i=0; i < rows; i++) 
   for (int j=0; j < cols; j++) 
     flow[i][j] = 1; 
The question is how does this water flow? The simplest way to model the flow is to assume that every single point in the grid distributes its water (initial, as well as incoming) to a single neighbor, namely its steepest downslope neighbor. If there are ties, they are broken arbitrarily. If a point is nodata, its flow should technically be nodata , but it is ok if you assign it to 1; In anycase, a nodata point does not send its flow to any neighbor, so all nodata points should end up with flow = 1( if they were assigned flow=1 initially). If a point has no neighbors that are lower than it, then it does not send its flow anywhere.

So you see, with this model water follows the steepest way down, either to the edge of the terrain or to a pit. Because water flows "down", there cannot be cycles.

The goal is to compute, for each point in the grid, the total amount of water that flows through that point.

I am providing the method

 boolean flowsInto(int i, int j, int k, int l)
which returns true if cell (i,j) flows into cell (k,l), that is, if cell (k, l) is the steepest downslope neighbor of cell (i,j). The way this method works is the following: it looks at all 8 neighbors of cell (i,j) and computes the lowest neighbor; this is the direction where water would go from cell (i,j). Then it checks whether this neighbor is equal to (k,l), in which case it returns true; otherwise it returns false.

The way I envision the computation of a flow of a cell (i, j), is that you will run a loop through all its neighbors, for every neighbor you will check whether it flows into the current cell, and if it does, you will add its value of the flow to the flow of the current cell. Note that a cell can receive flow from multiple cells. Also note that you will need to compute the value of the flow of the neighbor, which means that the problem is recursive.

The basic thing to note is when recursion stops. When no neighbor flows into the current cell there are no subsequent recursive calls.

The most important thing to think about is how to avoid computing the flow of a cell multiple times. One obvious way is to mark the grid: initially the entire grid is unmarked; whenever you determine the final value of the flow of cell (i,j), you set mark[i][j] = true. This way you know, if you ever need flow[i][j], whether you've computed it before or not. Marking the grid is just one way to do it ---it may make things clearer, or not. If you think you can do without marking the grid, do so.

For debugging reasons, you should first get test2.asc to compute flow correctly. Here is how the grid test2.asc looks like:

The grid is: 
 9 9 9 9 9
 8 7 6 7 8
 7 6 5 6 7
 6 5 4 5 6
 5 4 3 4 5
Here is what the flow should look like:
The grid is: 
 1 1 1 1 1
 1 2 4 2 1
 1 2 9 2 1
 1 2 14 2 1
 1 3 25 3 1
Note that point (4, 2) has height 3, and receives flow from all its neighbors, as it is the lowest neighbor for each of them. So 2 + 3 + 14 + 2 + 3 = 24. Add to this the initial 1 unit of flow at point (4, 2) and you got 25.

If you got test2.asc to run correctly, then your program is almost correct except maybe nodata values. If you feel like running it on the big grid set1.asc, here is how it will look like:

java Grid set1.asc

Email me the file when you are done. Have fun!