Disciplines such as environmental sciences, earth, oceanographic and atmospheric sciences deal with terrains and surfaces. In a computer, terrains (surfaces) are represented by sampling points on the terrain and recording the geographical coordinates {x, y} of the point and the coresponding elevation {z} of the terrain at that point. Thus, a terrain is represented as a cloud of points {x, y, z} (stored in a file).

Terrain data can be obtained by directly sampling the elevation of the terrain (this may be the most precise method, but the most time consuming, for sure!); by interpolating cartographic maps; from technology like LIDAR; or estimated from aerial photographs (SAR interpherometry).

Terrain models fall in different classes depending on how the points are distributed. The simplest and most widely used model is the regular square grid. A grid is a matrix of elevation points, sampled uniformly from the terrain.

Terrain elevation models are used to model and understand a wealth of natural processes in geosciences, and beyond.

In this lab you will extend your previous lab by adding functionality to compute the flow of water on a grid terrain.

The goal of flow modeling is to quantify how much water flows through the any point of the terrain; as a result, one can infer the location of the rivers (the points with a lot of flow) and th3 ridges (the points with low flow).

Knowledge about flow on the terrain turns out to be the fundamental step in many geographical processes. Unfortunately, flow data is not available from satellites, like elevation data. The location of rivers can be obtained by flying a plane and identifying rivers, but you can imagine that is a very very expensive process. That is why people have looked into ways to model flow based on the elevation of the terrain.

For a grid terrain, flow is expressed by a grid which has the same size as the elevation grid. The value of this flow grid at position (i,j) represents the total amount of flow that flows through point (i,j).

The computation of flow goes as follows: To start, every 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++) if point is not NODATA then flowGrid.set(i,j, 1); else flowGrid.set(i,j,NODATA);Here assume you have a setter on a Grid

The question is, where does the water go? 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 (undefined), its flow should be NODATA (undefined); A NODATA point does not send its flow to any neighbor, so all NODATA points should end up with flow = NODATA.
- If a point has no neighbors that are lower than it, then it does not send its flow anywhere. This point is (part of) a pit.

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.

To do this, the first method that you'll write will be:

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.

Once you have method `flowsInto` written *and tested*,
you can start working on computing flow. You will write a method that
computes a flow grid based on the (elevation) grid:

Grid computeFlow() { // create flow grid; you will need to write this constructor Grid flowGRid = new Grid (rows, cols, etc); //initialize flow grid for (int i=0; i< rows; i++) { for (int j=0; j < cols; j++) { ... } } //compute flow grid for (int i=0; i< rows; i++) { for (int j=0; j < cols; j++) { //compute flow of point (i,j) int flow = computeFlow(i, j); flowGrid.set(i,j, flow); } } return flowGrid; }Naturally, you will write a

Two things to think about when writing `computeFlow(i,j)`:

- Infinite recursion: is it possible for computeFlow to create a loop of recursive calls; if yes, how to avoid it.
- Efficiency: is it possible for the flow of a point to be computed multiple times. If yes, how to avoid it. 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. Maybe you can come up with a different way.

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 5Here 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 1Note 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.

This will work, but will not look that good. Basically, you need a diferent color map for flow than you need for elevation. For a perfect score, you would write a new render method especially for a flow grid. Why? On a flow grid the intervals/buckets for colors need to be much more aggressive, because there are very few cells with high values of flow. If you spread the range of heights uniformly over the color range, the rivers will not come up nicely.

Here is how the flow of set1.asc (could) look when rendered:

A more elegant way to do this, which I invite you to explore, is to define a Grid hierachy and use inheritance. Think of class Grid as handling a generic grid. You'd have Elevation Grid and a FlowGrid subclasses inheriting from Grid, both of them defining their specialized render methods.

Grid elev = new Grid("set1.asc"); elev.render(); Grid flow = elev.computeFlow(); flow.render();

- brunsdem.asc
- kaweah.asc
- sierra.asc
- test1.asc
- test2.asc
- srtm_21_05.asc
- srtm_22_04.asc
- Another site where you can download (more recent) data is: here. This is SRTM data collected by NASA SRTM mission in 2000 and released for free for research and educational purposes. It covers the entire world at 3 arc second resolution (approx. 90m). It comes in tiles. Download a tile in a location that you like.

Have fun!