All posts by mamont

Change Log: 0.7

  • The mechanism for combining two or more height meshes to obtain a great variety in planet relief forms. In particular, it is now possible to make continents with alternate mountainous and lowland areas.
  • Rivers are now more likely to flow into each other than flow in the same direction to the ocean.
  • River width variations fixed. Earlier, the value of the channel shift depended on the latitude.
  • The way of generating relief inside rhombs is again changed: sharp uplifts near the rivers are removed and there are no ubiquitous bulges.

The two new example planets added to the site.

Making river polygons with PostGIS

In the practice of the procedural generation of maps, I found it convenient to use the facilities introduced by PostGIS extender for PostgreSQL. One of the problems that arises, the creation of river polygons, has a fairly simple solution by means of PostGIS.

Here, I will give almost all code that is needed for making river polygons. Suppose, that we already have somehow obtained the rivers line segments of different streamflow (discharge) value. All river lines consist of such segments.

Also, we can assume that the segments with the same streamflow can be one or more for any river. More than one segments are needed to create variations in the width of the river channel (see the previous post).

If the database is new, then do not forget to add the PostGIS extension.


There are two tables in the database. One contains river segments and other contains resulting river polygons.

CREATE TABLE riversegs (
    rid           bigint NOT NULL,
    streamflow    integer NOT NULL,
    geom_wgs84    geometry(LINESTRING, 4326) NOT NULL

CREATE INDEX riversegs_idx ON riversegs (rid);

    rid bigint PRIMARY KEY,
    geom geometry(MULTIPOLYGON, 3785)

CREATE INDEX rivers_geom_idx ON rivers USING GIST (geom);

We specify different projections for fields of geometry type. EPSG:4326 or WGS 84 (World Geodetic System) projection is convenient for the initial creation of geographic objects during procedural generation, because its unit of measurement is usual degrees of longitude and latitude. EPSG:3785 (Web Mercator) projection is more convenient when it is necessary to make various transformations of geometries, and its unit of measurement is meter. Since the projections are different, at the certain stage we will have to make a transformation from one projection to another.

This code also includes the creation of two indices, the second of which is the spatial index. Such indices are a good tool for speed up many operations with geospatial data; see details in the PostGIS documentation. In this example, rivers_geom_idx index is not particularly useful, but if you intend to use the resulting rivers data further, then including it more than desirable. On the other hand, we do not create any spatial index with the geom_wgs84 field, since we do not need bounding box property of its values in our procedures.

The river line segments are of LINESTRING spatial type, and resulting rivers are of MULTIPOLYGON spatial type. We do not specify the POLYGON spatial type in favor of the more general type, since the rivers can consist of separate pieces, for example, if a lake divides the river into parts.

For contrived, but simple, example we write down the data of the river consisting of three segments.

INSERT INTO riversegs VALUES (1,1,ST_GeomFromText('LINESTRING(1 0,2 0,2 1,3 1)',4326));
INSERT INTO riversegs VALUES (1,2,ST_GeomFromText('LINESTRING(3 1,3 2,3 2,3 4)',4326));
INSERT INTO riversegs VALUES (1,3,ST_GeomFromText('LINESTRING(3 4,4 4,5 4,6 4)',4326));

Below is the main procedure in which we collect all possible rivers and write them into the rivers table.

        halfw double precision
    ) RETURNS void AS
    curs_rivers CURSOR FOR SELECT rid FROM riversegs GROUP BY rid;
    target_geom Geometry;
    FOR river IN curs_rivers LOOP
    SELECT INTO target_geom (
        SELECT ST_Multi(ST_Union(segs.geom))
            FROM (SELECT 
                makeBuffer(geom_wgs84, halfw, sqrt(streamflow)) as geom
                FROM riversegs
                WHERE rid=river.rid
            ) AS segs);
        target_geom = validateMultiPolygon(target_geom);
        INSERT INTO rivers VALUES (river.rid, target_geom);
    END LOOP river_loop;
$BODY$ LANGUAGE plpgsql;

The procedure takes one argument, the half value of the minimum river width in meters. Further, in the cycle for all possible rivers, we select all segments of each river and make the combined geometry with ST_Union function. We use the variant of ST_Union function which accepts a set of spatial objects as a single argument. With ST_Multi function we ensure that the geometry will be returned as a MULTI* geometry.

makeBuffer function is defined as following.

        geom Geometry,
        halfw double precision,
        sqrt_sf double precision
    ) RETURNS Geometry AS
    buffed_geom Geometry;
    w double precision;
    w = sqrt_sf * halfw;
    buffed_geom = ST_Buffer(
        ST_Transform(geom, 3785), w, 'endcap=round join=round');
    RETURN buffed_geom;
$BODY$ LANGUAGE plpgsql;

In this procedure we turn the river line segment into the ‘thick’ segment, which is a polygon. The function ST_Buffer helps us in this. 'endcap=round join=round' argument forces the function to use this rounding method for line joinings and endings. As the buffer width we take the product of half of the minimum width and the square root of the river current streamflow.

In accordance with the project’s agreement of semi-circular profile of the river bed, we take that the river width is proportional to the square root of the current streamflow. For the sake of justice, it should be noted that there are other ways of calculating the width of rivers depending on the streamflow.

Also, note how the function ST_Transform transforms our original geometry projection into another projection, in which the unit of measurement is meter.

The validateMultiPolygon procedure does a validation check and, if necessary, changes the geometry type to MULTIPOLYGON. If resulting geometry is invalid the procedure returns NULL. We try to get a valid object two times, firstly by ST_MakeValid procedure, and then by ST_Buffer procedure, which also is used to obtain valid geometries.

CREATE OR REPLACE FUNCTION validateMultiPolygon(geom Geometry)
    RETURNS Geometry AS
    new_geom Geometry;
    IF ST_IsValid(geom)=false THEN
        new_geom = findSubGeom(ST_MakeValid(geom));
        IF (new_geom IS NULL) OR ST_IsEmpty(new_geom)=true THEN
                new_geom = findSubGeom(ST_Buffer(geom, 1));
                WHEN SQLSTATE 'XX000' THEN new_geom = NULL;
        END IF;
        IF ST_GeometryType(geom)<>'ST_MultiPolygon' THEN
            new_geom = findSubGeom(geom);
            new_geom = geom;
        END IF;
    END IF;
    RETURN new_geom;
$BODY$ LANGUAGE plpgsql;

The last procedure tries to make MULTIPOLYGON geometry from input geometry; returns NULL if this can not be done.

CREATE OR REPLACE FUNCTION findSubGeom(org_geom Geometry)
    RETURNS Geometry AS
    new_geom Geometry;
    new_geom = NULL;
    SELECT ST_Multi(ST_Collect(a.gdump)) as b INTO new_geom
        FROM (SELECT (ST_Dump(org_geom)).geom AS gdump) AS a
        WHERE ST_GeometryType(a.gdump)='ST_Polygon';
    RETURN new_geom;
$BODY$ LANGUAGE plpgsql;

That’s all. As you can see, with four small procedures we were able to perform a difficult job. To start the process with minimum river width 100, just run the command

SELECT makerivers(50);

To view resulting data

SELECT ST_AsText(geom) FROM rivers;

More realistic rivers

Looking at the rivers from the previous post it seems that everything is quite pretty. But perhaps, like me, you feel that something is wrong. Oh ye, there are no such pretty smooth rivers in nature!

Let’s open google or other Earth maps and look at the shape of any river banks. Usually, the width of a river does not remain constant and gradually changes, even when the amount of water in it is invariably. Furthermore, the banks do not vary simultaneously. This, as well as the fact that such behavior of the banks does not depends on the width of the river, suggests that the shape of the river is a fractal (do not confuse this fractal with the fractal of the river system). All this is true for majority of small and medium-width rivers, but the widest lowland rivers often have more complex banks and have large well visible islands.

Here is a pair of random Earth rivers of medium width from two continents.

It is known that in nature there are almost no classical geometric forms, but fractal forms are very common.

So far we consider only the most frequent behavior of the river channel; ducts, deltas, islands, and old channels that have become oblong lakes we are not considering now. Although, the method proposed further, with some changes, can be applied to create models of these nature forms; at least I find it as promising.

Surely, there are already scientific works devoted to the formation of rivers and it would be possible to model river forms based on the strict physical theory. However, the shape of the mountains could be obtained by modeling based on the strict theory of lithospheric plates movement, weathering, and erosion; but, in order to obtain a plausible terrain for virtual worlds, no one does it (correct me if I’m wrong). For this purpose we use one of the algorithms of the Brownian surface generation.

Similarly, with rivers, we should try to get a fractal shape close to the real river forms, and then we could use it to represent procedural generated rivers on maps.

Again, carefully studying the real earth’s rivers, we can notice some special behavior of the river banks. Two banks most often do not diverge or taper simultaneously (it also happens, but less often). Everything looks so that one bank shore departs from the imaginary central line of the river or approaches to it. This phenomenon makes one think to put one more channel (river bad), different from the first but close to it, atop each other. Then we should get the right behavior of the banks.

So it turned out to be valid.

The second channel is constructed by the random walk method. Each point of the second channel is obtained by shifting the corresponding point of the first channel by a certain vector, which is the same in absolute value (for the current river width), but its direction makes a random walk. When passing to each next point, we add a random value to the last angle value, and the random values is distributed according to the normal (Gaussian) distribution. The points thus obtained are also smoothed.

Obtained channels must be merged in the resulting river polygon. How exactly I merge channels I will recount in the next post. For now a few examples from the planet Admete.

Click the images for bigger image size.

Rivers And Lakes

This is the first of three posts, where I will describe the process of creating river systems in the project. In this post the algorithm for constructing and smoothing rivers will be described, in general terms. The second post will contain information on how to make river banks more realistic. And the third post will be with the program code, which is used to create the final river form.


Firstly, some information about the lakes. In the procedural generation of maps, as a rule, lakes are formed at the moment when sea level is defined. Areas below a certain height is considered water, higher areas are land. Isolated parts of water are lakes.

But in this case we do not take into account all hollows (pits) in the terrain which are above sea level. In nature there can be lakes in such places and rivers can flow into these lakes.

In the project I make a lake in almost each terrain pit, sometimes there is a large lake covers a few pits. It is possible to regulate the limits of absolute or relative depth (in relation to the depth of the pit) of lakes. At the moment, the amount of water flowing into the lake does not affect the size of it and there are no outflowing rivers from lakes. Implementation of these features seems rather complicated, so I postponed it for later. 🙂

Those lakes that are obtained by setting the sea level are also present in the project and are called lowlakes.

River systems

As you can learn from previous posts, the relief is constructing on some two-level regular lattice of points. At the lower level, these are the points inside a rhomb on the projection cone.

At first the river is made as some curved line on the lattice points. The resulting line is called the river line. To put simply, the algorithm for creating a river line is as follows:

  • In each rhomb we randomly select the source point of the new river and then choose the random outflow point on the rhomb edge. Here we must ensure that the height of the outflow point is less than the height of the source point. In the next versions of the project there will be several river sources for a rhomb and their number will vary depending on the type of the terrian and, possibly, the type of the biome.
  • The line inside the rhomb is drawn by the brownian motion simulation method (not exactly what’s on the link but close). Possible loops are eliminated.
  • In subsequent rhombs we draw a line from the source, which corresponds to the outflow point in the previous romb, to the new outflow point.
  • This process continues until we flow into either the ocean, or in a lake pit, or into another already made river (trunk).

There is the special order in drawing river lines, firstly we should draw rivers which have source far from the ocean and in the mountains. By this we achieve what is observed in the Earth rivers: the most big rivers begin in the mountains or far from the ocean.

The river line is far from what we would want to see on the map. To represent the river on the map few more things are required.

  • Smoothing of the river line.
  • Giving the river a width and turning it into a polygon.

In the project I use a simple and effective method of smoothing, which consists in aligning each point as a midpoint between the previous point (already aligned) and the next point (not yet aligned). To better preserve the characteristic behavior of the original line we can pre-insert additional midpoints in the original line, and then make smoothing.

The images of the original (typical) river line and line recieved after smoothing are on this picture.

As it turned out, with the increase in the ‘thickness’ of the river, additional points become excessive, and for the thickest rivers even the initial points of the river line are too many; such rivers become more like a long caterpillar. Therefore, for wide rivers, some points are firstly evenly removed, and the remaining ones are smoothed.

How to draw a line of a suitable width from a thin river line we will study in the third post. Now you can just look at the results on the three pictures below.

On the first picture a river of minimum width of those that are currently depicted on the planets. The second and third pictures show rivers which are by two and four times wider than the first. Respectively, there are only every second and every fourth point remains in them, remainder points are aligned. For rivers with an intermediate width, the uniform elimination of points on the interval can be applied.

Change Log: 0.6.1

  • Rivers get some random variance in their width along the stream.
  • riversz shapefile also gets the trunk field which contains the id of the trunk river in which this river flows into. The special value -3 denotes that this river flows into the ocean. Currently, rivers in riversz shapefile do not take into account lakes.
  • PNG tiles and shapefiles of Admete planet was updated respectively.

In the next 2-3 posts I will describe the process of river constructing. And for a while the river system will remain as it is. At the beginning of the new year I will try to make the planet continents look more like Earth continents with intermittent mountainous and flat areas, and not with one or two central mountains as it is now. The resolution of the planets will still be small (as it is now), it will be increased only after all the significant problems will be solved.