You are on page 1of 17

Generating Terrain Layout, Map Coloring and Water

Flow Simulation
Rãzvan Remus Popovici
July 21, 2003

1 Abstract
Many GIS applications require terrain information. Since terrain information are sometimes
very expensive, sometimes secret, for testing and demonstrating them it can be needed to use
randomly generated terrain configuration.
A representation of the terrain information can be a colored a 2D map, or a miniature
model. Current approach uses graphical capabilities of the ”Matlab” product for creating a
realistic 3D representation of the terrain data.
As a sample of GIS algorithm, it is presented a simulation of water flow. You can see how
river, lakes or seas are generated on your terrain.

2 Generating Terrain Information


In practice, when in need to represent a terrain map, first step would be to measure the altitude
of different places in the field. More methods can be used.
First method exists maybe since a few hundreds years. It consists in identifying some
shapes in terrain that have the same altitude. On the map, those shapes are represented
together with their altitude. By example, a perfect conical mountain would be represented as a
set of concentrically disposed circles. The altitude would increase while the radius of the circles
will decrease.
In the modern times, the satellite measuring offers us a network of points, the size of the
quadrat edge in the network can be even one meter. With this information, it can be practically
determined the altitude of each point of the field, by using an interpolation mechanism.
For randomly generating the map, we cannot randomly generate the altitude of each point
in the grid. Simply the map won’t be realistic. The proposed method is to choose randomly
some point in the map, named pivots, and to generate some random altitudes for them. The
rest of the points would be determined through¡ interpolation.
So, let it be: Xn and Yn the points and H is the randomly generated altitude vector. With
other words, the point with the coordinates (Xi , Yi ) has the altitude Hi .

1
For determining the altitude of a certain (x, y) coordinated point, further labelled with P ,
we are first checking if the point does not identify itself with a (Xi , Yi ) pair. If so, the altitude
is or course Hi .
Further, using the Pythagoras theorem, we are determining the distance between P and the
other points:
q
di = (x − xi )2 + (y − yi )2
We can surely state than d vector is strictly positif so not null, then 1/di has always a real
and positive value. We will use the 1/di as weight of average for existing given heights. In
practice, for a large area, we need only to take the given heights that are in the nearby of the
point, because 1/di is decreasing faster as di grows, the the weight of farther points wouldn’t
be significant for the formula.
Then, the interpolated formula would be:
Pn Hi
1 dγi
h( x, y) = Pn 1
1 dγi

γ is a variance coefficient, determined experimentally. Here are a pair of experimental


samples, with various γ factors:
γ = 2 (realistic)

γ = 3 (also realistic)

2
γ=4

γ = 0.5

γ=1

3
γ = 1.5

Also, a different terrain type can be obtained by choosing to generate a smaller or bigger
size of X and Y vectors.
About the complexity of the algorithm, it needs Θ(s2 ) for iterating all the points of the
quadratic array (s is the size of the array) and for each point the determination complexity
is Θ(n), with n number of randomly chosen altitude points. Then, the total complexity is
Θ(s2 · n).
Since the complexity is polynomial, it is satisfactory. An optimization still can be done. As
previously stated, the very far points are not influencing too much the height of a point. Then,
we can choose a fixed number t of points for which the measurement is accurate. We will only
compute this t points. For this, we need to draw a grid, which will assure us that around t/6
pivot points are present in each quadrat of the grid. Since the coordinates of this points are
random, we can expect their distribution to be uniform.
The edge size of the grid, g, can be determined by:
t
g·g· =n
6

4
Because g · g is the total number of squares, t/6 is the average expected number of squares.
Then:
s
6·n
g=
t
n
If g is greater as 3 then the algorithm is inefficient, but this would happen only when n
(number of pivots) is small, then practically the complexity would be also O(s2 ).
Also, when g is smaller as 1, it means than the number of pivot is too big, almost for each
point existing a pivot. In this case, interpolation would run also fast, since most of the points
are already determined.
Next, for each point P to interpolate, we would consider the current grid quadrat and the
neighbors quadrates, so we would use the pivots for 9 quadrates. Therefore in average 9·t 6
pivots would be used, then almost always our requirement of t pivots would be satisfied. The
complexity would be in this case:
9·t
Θ(s2 · ) = Θ(s2 · t) = O(s2 )
6
We are considering t a hard-coded constant, for example 10, independent for the size of the
map or the amounts of the points.
Since the interpolation is done independently for each point, for a piece of the surface, the
complexity would be
Θ(a · b)
with a and b the length and width of the surface.
The matlab program for altitude interpolation follows. Here the P oints(maxpoint, 2) takes
the role of our X and Y vectors. Also Hights is the H vector. Surf ace is the output of the
block, it represents the network of points, determined using interpolation.

for i = 1:arraydim
for k = 1:arraydim
% determine the vector of distances from current point to all other points
dists=zeros(maxpoint,1);
inited=0;
for z=1:maxpoint
x=i-Points(z,1);
y=k-Points(z,2);
dists(z)=sqrt(x*x + y*y);
if (dists(z)==0)
Surface(i,k)=Hights(z);
inited=1;
end;
end;
if (not(inited))
% interpolate the actual point
weight= 100 ./ (dists .^ aspect);

5
Surface(i,k)=weightavg(Hights,weight);
end;
end;
end;

The weighted average function is:

function y=weightavg(V,W) % Values and weight


y = sum(V.*W)/sum(W);

3 Coloring Terrain Map


Coloring the map is a linear interpolation problem.
As coloring convention, will be considered white for abysses, green for fields at average
altitude, soft brown for hills, deep brown for mountains, and black for peaks.

mywhite=[1,1,1];
waterblue =[9/255,19/255,150/255];
plaingreen=[4/255,172/255,4/255];
hillbrown=[106/255,128/255,23/255];
highmountain=[45/255,23/255,11/255];
myblack=[0,0,0];

First step is to determine the altitude interval of the colored zones. Easiest possible deter-
mination is the field, it is the arithmetical average of the whole altitude array. What is under
the average level will be considered abyssal area and will turn white with the depth. The end of
fields will be when their altitude goes over 120% of the average altitude. The mountain peaks
will count only when they go over 90% of the maximum altitude of the map.
We are
 building following coloringmatrix:
deep mywhite
 


aver plaingreen 

C =  aver*1.20 hillbrown .

 
 peak*0.90 highmountain 
peak+1 myblack
For a given altitude h, we are searching in altitude column, (first column of the Coloring
matrix), the row i with the property:
C[i, 1] ≤ h < C[i + 1, 1].
Once the row found, we need only to determine the color of the point (c) by applying the
following formula:
c = C[i+1,2]∗(h−C[i,1])+C[i,2]∗(C[i+1,1]−h)
C[i+1,1]−C[i,1]
In matlab:

for i = 1:arraydim
for k = 1:arraydim

6
for c=1:length(Size)-1
if (Surface(i,k)>=Size(c))
Col(i,k,:)=(colors(c,:)*(Size(c+1)-Surface(i,k))+colors(c+1,:).*
(Surface(i,k)-Size(c)))./(Size(c+1)-Size(c));
end;
end;
end;
end;

Please note that the c and C(:, 2) are size three vectors, then the upper formula must be
used three times, for all three RGB components.
Applying iteratively the formula for the whole grid, we are obtaining the desired terrain
simulation.

7
Next picture is a ”plane view”. It is similar with classical paper map.

8
The complexity of coloring is Θ(1) for each point, since the C matrix has a fixed hard-coded
size independent from the other measures of the map. Then, for coloring the whole map, the
algorithm complexity is
Θ(s2 )
. Since the coloring is done independently for each point, for a piece of the surface, the
complexity would be
Θ(a · b)
with a and b the length and width of the surface.

4 Simulating the Water Flow


Following, we are going to present an algorithm that simulates the flowing of a river in our
map.
We choose randomly the R(x,y) point in the map. From here the river would flow until it
would reach the margin of the map. If the river would meet a cavity, it needs to fill it out,
forming a lake. Once a new possibility is found, the river would continue to flow.
The used algorithm is ”Greedy” class. We are considering the current water level h and the
vector W that keeps the coordinates of the current ”lake”. For beginning, the W = (R((x, y))
and the h = Surf ace(x, y) , where Surf ace is the altitude array.
The algorithm stops when there are one or more points in W that are staying on the edge
of the map; the mathematical condition is:

∃X(x, y) ∈ W |x = 1, or y = 1 or x = n or y = n
For the W set, we are determining a W 0 set, with the neighbors of the points in W. Math-
ematically:

W 0 = {X(x, y)|X ∈
/ W and ∃A(a, b) ∈ W |x = a − 1 or x = a + 1 or y = b − 1 or y = b + 1}

9
next, we are determining the h1 , by

h1 = min(Surf ace(X)|X ∈ W 0 )
h1 represents the minimal altitude in the neighborhood of the set of points W . It is obvious
than the water will flood this / those point(s). Let W 00 be the set of the minimal altitude points
in W 0 .

W 00 = {X ∈ W 0 |Surf ace(X) = h1 }
The flooding would happen in two forms:
1. h1 < h This means that the point is lower then the actual lake. Then, the whole mass
of water will go through W 00 . Therefore, in the next iteration we would consider W = W 00 and
h = h1
2.h1 ≥ h This means that the minimal point is higher or on the same level then the actual
lake. Thus the water will flood the W 00 points, but the actual lake will remain, and it would
raise its level up to h1 . Therefore, in the next iteration we would consider W = W 00 ∪ W and
h = h1 .
The steps would be repeated until the terminating condition is satisfied. The condition
would be met, because each iteration it is set a new ”dry” point under water. Therefore, the
maximum number of iterations is (s − 1)2 . The iteration complexity is θ(s2 ).
For implementing this algorithm, we would keep the W sets in an array B = 0s . Size
of this array would be the surface size, s. For each member X(x, y) from W would be the
corresponding B(x, y) = 1.
Next, the elements described in W 0 would be initialized with 2. Determining those elements
is relatively simple, we only need to iterate the B matrix, and for each null point to check
if there is any neighbor non-null in nearby. The determination have the θ(s2 ). As a next
iteration, we are determining the h1 as the minimum of the Surf ace elements, that correspond
to a B(i, j) = 2. Once determined, one more iteration would label with 3 those elements in B
which satisfy h1 = Surf ace(i, j).
Advancing to next step of the algorithm:
1. h1 < h all points in B that have the value 3 would get the value 1 and the rest would be
initialized with 0.
2.h1 ≥ h all points in B that have the value 3 would get the value 1 , the points with 1 would
remain unchanged and the rest would be initialized with 0. All the points (i, j) in surf ace that
correspond to a B(i, j) = 1 would receive the value h1 because of the flooding.
Check the exit condition and loop.
The complexity of the algorithm is Θ(s4 ) because of the product of Θ(s2 ) resulted from
loops number and Θ(s2 ) representing the complexity of a loop. s represents the size of the edge
of the quadratic map.
For improving the algorithm speed, we will associate to each of the vectors W and W 00 four
variables: xmin , xmax , ymin and ymax . They are going to represent the edges of the current set.
Computing those numbers won’t increase the complexity, since we are iterating the W and W”
vectors for other purposes. The advantage would be we only need to parse for neighbors a
limited zone in B, delimited of xmin − 1, xmax + 1, ymin − 1 and ymax + 1. The improvement

10
is only of practical nature, since the difference between xmax , and xmin respectively ymax and
ymin is in the worst case in the Θ(s) class, but in the best case even 0. Also, it is guaranteed
that not all the points would be flooded, so Θ(s2 ) is only the worst case can happen. Therefore
the complexity of the algorithm is O(s4 ).

11
Following, the complete matlab listing for generate terrain, coloring the map and simulation

12
of the water flow.

function terrain

% globals

maxpoint=14; % number of known points


arraydim=60; % size of the table
maxhight=700; % highest possible peak
aspect=2; % 0.5=only peaks 1 = Vulcanic 2 =Himalaya 3=Alpen 4=Ardeni

% generate the terrain

% measurement coordinates
Points=rand(maxpoint,2) .* arraydim +1 ; for k = 1:2
for i = 1:maxpoint
Points(i,k)=round(Points(i,k));
end;
end;

%measures results
Hights=rand(maxpoint,1) .* maxhight ;

%create the board


Surface=zeros(arraydim,arraydim);

for i = 1:arraydim
for k = 1:arraydim
% determine the vector of distances from current point to all other points
dists=zeros(maxpoint,1);
inited=0;
for z=1:maxpoint
x=i-Points(z,1);
y=k-Points(z,2);
dists(z)=sqrt(x*x + y*y);
if (dists(z)==0)
Surface(i,k)=Hights(z);
inited=1;
end;
end;
if (not(inited))
% interpolate the actual point
weight= 100 ./ (dists .^ aspect);

13
Surface(i,k)=weightavg(Hights,weight);
end;
end;
end;

% color the map

%define some colors


mywhite=[1,1,1]; waterblue =[9/255,19/255,150/255];
plaingreen=[4/255,172/255,4/255]; hillbrown
=[106/255,128/255,23/255]; highmountain =[45/255,23/255,11/255];
myblack=[0,0,0];

% determine min/max/avg of the current terrain


peak=max(max(Surface)); deep=min(min(Surface));
aver=sum(sum(Surface))/(arraydim*arraydim);

% build colormap the vectors


Size= [deep ,aver, aver*1.20,peak*0.90 ,peak+1 ];
colors=[mywhite;plaingreen;hillbrown;highmountain;myblack];

%determine the colors of the points in the map


Col=zeros(arraydim,arraydim,3);

for i = 1:arraydim
for k = 1:arraydim
for c=1:length(Size)-1
if (Surface(i,k)>=Size(c))
Col(i,k,:)=(colors(c,:)*(Size(c+1)-Surface(i,k))+colors(c+1,:).*
(Surface(i,k)-Size(c)))./(Size(c+1)-Size(c));
end;
end;
end;
end;

% the water
water=(rand(2,1) .*(arraydim-20))’; for k = 1:2
water(1,k)=round(water(1,k))+10;
end;

% draw the map


surf(Surface,Col,’EdgeColor’,’none’);
%return - quit here for the relief

14
waterarray=zeros(arraydim,arraydim);
waterarray(water(1,1),water(1,2))=1;

high=Surface(water(1,1),water(1,2));

while(1)
% stop condition
ex=0;
[i,j,]=find(waterarray);

for i1 = 1:size(i,1)
Col(i(i1),j(i1),:) = waterblue;
if (i(i1)==1) | (j(i1)==1) | (i(i1)==arraydim) | (j(i1)==arraydim)
ex=1;
break;
end
% dilate the point
x=i(i1);y=j(i1);
z=[x-1,y-1; x-1,y; x-1,y+1; x ,y-1 ;
x, y+1; x+1,y-1; x+1,y; x+1,y+1];
for c = 1:8
if (waterarray(z(c,1),z(c,2))==0)
waterarray(z(c,1),z(c,2))=2; % marked for min
end
end

end

if ex==1
break;
end

% find out the min


[i,j,]=find(waterarray==2);
onldmin=high;
high=maxhight+1;
for i1 = 1:size(i,1)
if Surface(i(i1),j(i1))<high
high=Surface(i(i1),j(i1));
end
end

% determine the min points

15
for i1 = 1:size(i,1)
if Surface(i(i1),j(i1))==high
waterarray(i(i1),j(i1))=3;
Col(i(i1),j(i1),:) = waterblue;
end
end

[i,j,]=find(waterarray);
% determine the flowing strategy: flood or flow
if (high>onldmin)
% flooding
for i1 = 1:size(i,1)
x=i(i1);
y=j(i1);
if (waterarray(x,y)==2)
waterarray(x,y)=0;
end
if waterarray(x,y)==3
waterarray(x,y)=1;
end
if waterarray(x,y)==1
Surface(x,y)=high;
end
end
else
% flowing
for i1 = 1:size(i,1)
x=i(i1);
y=j(i1);
if (waterarray(x,y)==2) | (waterarray(x,y)==1)
waterarray(x,y)=0;
end
if waterarray(x,y)==3
waterarray(x,y)=1;
end
end
end

% show the progess of the map


%surf(Surface,Col,’EdgeColor’,’none’);

end;

16
surf(Surface,Col,’EdgeColor’,’none’);

return

function y=weightavg(V,W) % Values and weight


y = sum(V.*W)/sum(W);

17