Last update: 10 April 2006
This page provides links to all the Maps of Dekavur, with some explanations. The Maps are about 500Kb in size and occupy 1200x815 pixels; one pixel equals 1 kilometre.
If you want to know how I made them, go here for some tips.
This Map shows the provinces into which Dekavur is divided and the towns with five thousand or more inhabitants. The names of the towns appear in four sizes, indicating populations of greater than 50 thousand, 20 thousand, 10 thousand, and 5 thousand. The name of the capital of each province is capitalised.
I was lucky, not only to be able to work out a colouring of the provinces which used four colours, but also to get roughly the same area with each colour.
The colouring indicates the height above sea level; the gradations are 200 (green), 500 (yellow), 1000 (yellow-orange), 2000 (orange), and 4000 (lilac) metres.
The Map also shows the names of the seas, the mountain ranges, and the principal rivers. As you can see, the text is ugly and sometimes unreadable, but it's better than nothing :-/ I don't have a program which has a decent "fit text to path" algorithm, so I had to knock one up myself, and this is the best that I can do; if anyone wants to put the text on properly, I'll be very grateful.
The Maps are made up of several layers blended together, with text added where appropriate. This was done, not with a program like The Gimp, but with a program I wrote myself; good as The Gimp is, it isn't terribly convenient to handle over a hundred separate layers for all the text.
If you are going to write a program yourself to do all this, you'll need a toolkit which provides access to images at individual pixel level. I used Python, the Qt widget set from Trolltech, and the PyQt bindings from Riverbank Computing.
The political Map is made up of three separate layers. From bottom to top, they are:
The physical Map is similarly composed, except that the second layer is colored according to the height above sea level. I did this by starting with a heightmap and converting each height to the appropriate colour. The heightmap is a rectangular array of heights which represents the area covered by the Map, together with an indication of whether the height applies to land or water; it was generated with the predecessor of my program q3dv, and is necessary if you want to do shading.
The magnitude of a vector A is represented as |A|, and equals the square root of the sum of the squares of the components: |A| = sqrt(Ax2 + Ay2 + Az2). If A and B are (1, 2, 2) and (2, 3, 6), |A| = 3 and |B| = 7.
The dot product of two vectors A and B is written as A.B; it is defined as the sum of the products of their respective components. Thus A.B = AxBx + AyBy + AzBz, and the dot product of the two vectors above is 1x2 + 2x3 + 3x6 = 26. It equals the product of the magnitudes of the two vectors multiplied by the cosine of the angle between them; the dot product of two perpendicular vectors is thus zero.
Two non-parallel vectors define a plane, i.e. any point in a plane can be located with two non-parallel vectors in that plane. This is the basis of Cartesian coordinate geometry. Similarly, three non-parallel vectors define a space; for example, A = Axi + Ayj + Azk.
The cross product of two vectors is written as A x B and defined as A x B = (AyBz + AzBy, AzBx + AxBz, AxBy + AyBx). This vector is perpendicular to both A and B, and its magnitude equals the product of the magnitudes of the two vectors multiplied by the sine of the angle between them.
-- Zn -- Zw Zc Ze -- Zs --
The first vector we need gives the position of the Sun. If the Sun is d degrees above the point at angle a of the horizon, where 0 degrees represents east and 90 north, this can be calculated as follows:
S = (cos(a) x cos(d), cos(a) x sin(d), sin(d)
If the Sun is on the horizon at due east, north, west, and south, S will equal respectively (1, 0, 0), (0, 1, 0), (-1, 0, 0), and (0, -1, 0). Directly above is represented by (0, 0, 1).
The second vector is N, the normal at the centre of the interesting point of the heightmap; this is the vector which points "straight out" of the surface, perpendicular to all tangents. It can be calculated using the cross product of two appropriate vectors; but how do we get the "appropriate" vectors? The most accurate method would fit cubic splines to the surface, but this is unnecessarily complicated for our purposes; we can make life much easier with quadratic splines, which merely require the other Z values. We take the two vectors which pass through the neighbouring vectors horizontally and vertically:
U = (2, 0, Ze - Zc) and U = (0, 2, Zn - Zs)
CAREFUL!!! These Z values must be scaled to use the same units as the horizontal and vertical resolution (the distance between adjacent points) of the heightmap. If, like me, your heightmap has a resolution of 1 kilometre and represents heights in metres, this means dividing the Z's by 1000.
These two vectors define a plane; we take their cross product to find N:
N = U x V = (2(Zw - Ze), 2(Zs - Zn), 4)
Now, if N and S are parallel, the point should receive maximum illumination, and if they are perpendicular, the point will receive none. This means that we need to find a, the cosine of the angle between N and S; to make things slightly easier, |S| happens to be 1:
t = cos(a) = (N.S) / |N|
The value of t will be between 1 and -1; negative values indicate "no illumination" and should be converted to 0. You can now draw the shaded version of the point simply by multiplying the red, green, and blue RGB values you would normally use for the unshaded version of the point by t.
Note that this method will not take account of shadows cast by high objects; for that you need to get into ray-tracing. However, the results are good enough for me :-) It's fun, and quite impressive, to watch how the shading changes as you move the Sun around.