Contour relief maps using mapnik

From OpenStreetMap Wiki
(Redirected from Contours)
Jump to navigation Jump to search

In February 2008 contours were implemented on the cycle map (a Mapnik rendered Slippy Map). A similar approach was then used for OpenPisteMap, which was then copied by the cycle map in turn.

In both cases, a "toolchain" was set up, such that ongoing OSM data improvements are re-rendered along with the contour graphics at regular intervals. Rendering happens for the entire set of tile images (at several zoom levels), which makes it quite an intensive process in terms of CPU/memory and storage. This page describes the toolchain, and various other considerations.


Toolchain

Cycle contours.png

In summary

  • Download SRTM data from NASA
  • Prepare for gdal_contour
  • Run gdal_contour to create contour lines
  • Store the contours in either shapefiles or postgis
  • Add layer definitions for mapnik

Downloading data from NASA

We're interested in srtm v2 SRTM3 from [1]. Version 1 of the srtm data had contours over the oceans - version 2 is clipped to land regions. SRTM3 covers the whole world between 60S and 60N - SRTM1 is higher resolution for the US but we're sticking with SRTM3 even in North America.

The data comes in zip files covering 1degree x 1degree sections of the planet at 1201x1201 pixels of spot-height data. So download each .hgt.zip file you are interested in. At the time of writing we had 248 zip files.

wget http://dds.cr.usgs.gov/srtm/version2_1/SRTM3/Eurasia/N54W001.hgt.zip
etc...

The openstreetmap-carto approach

This is approach adds contours to the standard OpenStreetMap mapnik-rendered view using CartoCSS, as defined in the openstreetmap-carto repository. The commands assume that you are using the Docker-based setup as described in the openstreetmap-carto DOCKER.md instructions. It assumes a Debian/Ubuntu Linux machine for preparing the contour files.

Prerequisites

The Python 'elevation' library is used to download SRTM data, GDAL/OGR is used to create contours and shp2pgsql (provided by postgis) is used to load them into the database.

sudo apt install python-pip gdal-bin postgis
sudo pip install elevation

Generate contours

The following steps download a DEM (30 m pixels) from the Shuttle Radar Topography Mission (SRTM) project and calculate contours.

# Download data
eio clip -o srtm_30m.tif --bounds -61.52 12.55 -61.01 13.50

# Calculate 10 m contours
gdal_contour -i 10 -a height srtm_30m.tif contour.shp

Load contour data to database

Ensure that the database is running and that you can connect to it. You can test connection with:

psql -h <hostname> -U postgres -d gis -c 'SELECT 1;'

The data are loaded via shp2pgsql by running the following commands within the shapefile directory:

cd srtm_30m_contours_10m
shp2pgsql -p -I -g way -s 4326:3857 contour.shp contour | psql -h localhost -p 5432 -U postgres -d gis
shp2pgsql -a -g way -s 4326:3857 contour.shp contour | psql -h localhost -p 5432 -U postgres -d gis

This creates a table called contours in the public schema in the Web Mercator projection.

Update the CSS files

Create a file in the openstreetmap-carto directory named contours.mss with the following content:

@contour: lighten(brown, 30);
@contours-text: lighten(brown, 30);

@contours-line-width: 0.5;
@contours-line-smooth: 0.9;   // A value from 0 to 1

@contours-medium-multiplier: 1.5;
@contours-major-multiplier: 2.0;


#contours10[zoom>=16] {
  line-color: lighten(@contour, 10);
  line-smooth: @contours-line-smooth;
  line-width: @contours-line-width;
  line-opacity: 0.4;
}

#contours50[zoom>=14] {
  line-color: lighten(@contour, 10);
  line-smooth: @contours-line-smooth;
  line-width: @contours-line-width * @contours-medium-multiplier;
  line-opacity: 0.5;
}

#contours200[zoom>=12] {
  line-color: @contour;
  line-smooth: @contours-line-smooth;
  line-width: @contours-line-width * @contours-major-multiplier;
  line-opacity: 0.5;
}

#contours-text50 {
  text-name: "[height]";
  text-face-name: @book-fonts;
  text-placement: line;
  text-fill: @contours-text;
  [zoom >= 16][zoom < 20] {
    text-spacing: 1000;
    text-size: 10;
  }
}

#contours-text200 {
  text-name: "[height]";
  text-face-name: @book-fonts;
  text-placement: line;
  text-fill: @contours-text;
  text-halo-fill: @standard-halo-fill;
  text-halo-radius: @standard-halo-radius;
  [zoom >= 12][zoom < 14] {
    text-spacing: 6000;
    text-size: 8;
  }
  [zoom >= 14][zoom < 20] {
    text-spacing: 1000;
    text-size: 10;
  }
}

Update the Stylesheet section of project.mml by inserting the following:

  - contours.mss

Update the Layer section of the project.mml file by inserting the following, just before the landcover-line section:

  - id: contours10
    geometry: multilinestring
    <<: *extents
    Datasource:
      <<: *osm2pgsql
      table: |-
        (SELECT
            way
         FROM contour
         WHERE height::int % 50 != 0 AND height::int % 200 != 0
        ) AS contours10
    properties:
      minzoom: 16
  - id: contours50
    geometry: multilinestring
    <<: *extents
    Datasource:
      <<: *osm2pgsql
      table: |-
        (SELECT
            way
         FROM contour
         WHERE height::int % 50 = 0 AND height::int % 200 != 0
        ) AS contours50
    properties:
      minzoom: 14
  - id: contours200
    geometry: multilinestring
    <<: *extents
    Datasource:
      <<: *osm2pgsql
      table: |-
        (SELECT
            way
         FROM contour
         WHERE height::int % 200 = 0
        ) AS contours200
    properties:
      minzoom: 12
  - id: contours-text50
    geometry: linestring
    <<: *extents
    Datasource:
      <<: *osm2pgsql
      table: |-
        (SELECT
            way, height
         FROM contour
         WHERE height::int % 50 = 0 AND height::int % 200 != 0
         ) AS contours_text50
    properties:
      minzoom: 16
  - id: contours-text200
    geometry: linestring
    <<: *extents
    Datasource:
      <<: *osm2pgsql
      table: |-
        (SELECT
            way, height
         FROM contour
         WHERE height::int % 200 = 0
        ) AS contours_text200
    properties:
      minzoom: 12

Start kosmtik to view

As per the openstreetmap-carto DOCKER.md instructions, kosmtik can be started as follows:

docker-compose up -d kosmtik

Kosmtik should be visible by browsing to http://localhost:6789, allowing you to inspect the contours.


The shapefiles approach

There are two approaches in use to render contours onto the map. The cycle map orignially stored the contours as shapefiles, as described here, but has since moved to storing the contours in the postgis database alongside the main osm data.

Preparing for gdal_contour (unnecessary with recent gdal)

Old versions of gdal can't read the .hgt files directly. In this case, you can convert the .hgt files into .tiff files using the srtm_generate_hdr.sh script.

Use gdal_contour to generate shapefiles

For the cycle map, we want to render the 10m, 50m and 100m contours separately, so for every .zip file we'll generate 3 shapefiles. The gdal_contour command is along the lines of:

gdal_contour -i 10 -snodata 32767 -a height N54W001.tif N54W001c10.shp

-i sets the contour interval. -snodata is needed to override the void detection, otherwise it thinks the voids are actually 32km high spikes (so you get very dense circular contours around the voids). -a height means that the height of each line will end up in the shapefile, otherwise it'll just be a collection of lines with no actual data.

The previous two steps can easily be scripted, as per process.sh (Note: srtm_generate_hdr.sh is not needed with newer Gdal, the script should be adapted):

#!/bin/bash

cd /home/osm/shape-resources/srtm/
for X in *.hgt.zip
do
    yes | /home/osm/andy/srtm/srtm_generate_hdr.sh $X
done

rm *.hgt #remove unzipped .hgt files, not needed

rm *.shp #remove old shapefiles so that gdal_contour doesn't barf
rm *.shx
rm *.dbf

for X in /home/osm/shape-resources/srtm/*.tif
do
    echo $X
    gdal_contour -i 10 -snodata 32767 -a height $X ${X%%.tif}c10.shp
    gdal_contour -i 50 -snodata 32767 -a height $X ${X%%.tif}c50.shp
    gdal_contour -i 100 -snodata 32767 -a height $X ${X%%.tif}c100.shp
done

Index the shapefiles

The shapefiles can be given a spatial index, which significantly speeds up the rendering (since mapnik can check the index and ignore shapefiles that are outwith the bounds of the particular tile it's rendering. If you've got mapnik installed, you'll have the shapeindex utily, and you'll be able to

index.sh:

#!/bin/bash

for X in /home/osm/shape-resources/srtm/*.shp
do
   shapeindex ${X%%.shp}
done

Generate the mapnik layers

You'll need a layer definition for every shapefile. Given that we're going to have different styles for each set of contours (10, 50, 100m) we need to correlate them. So the two styles (for lines and text) are suffixed by the set number. The y iterator simply makes sure the layers have unique names.

generatexml.py:

#!/usr/bin/python
import glob

f = open('../osm-cycle/contour-layers-c100.include', 'w')
y = 0
for X in glob.glob('/home/osm/shape-resources/srtm/*c100.shp'):
  f.write("<Layer name=\"srtm100"+`y`+"\" status=\"on\" srs=\"+proj=latlong +datum=WGS84\">\n")
  f.write("  <StyleName>contours100</StyleName>\n")
  f.write("  <StyleName>contours-text100</StyleName>\n")
  f.write("  <Datasource>\n")
  f.write("    <Parameter name=\"type\"><![CDATA[shape]]></Parameter>\n")
  f.write('    <Parameter name="file"><![CDATA[' + X + "]]></Parameter>\n")
  f.write("  </Datasource>\n")
  f.write("</Layer>\n")
  y = y + 1
f.close

f = open('../osm-cycle/contour-layers-c50.include', 'w')
y = 0
for X in glob.glob('/home/osm/shape-resources/srtm/*c50.shp'):
  f.write("<Layer name=\"srtm50"+`y`+"\" status=\"on\" srs=\"+proj=latlong +datum=WGS84\">\n")
  f.write("  <StyleName>contours50</StyleName>\n")
  f.write("  <StyleName>contours-text50</StyleName>\n")
  f.write("  <Datasource>\n")
  f.write("    <Parameter name=\"type\"><![CDATA[shape]]></Parameter>\n")
  f.write('    <Parameter name="file"><![CDATA[' + X + "]]></Parameter>\n")
  f.write("  </Datasource>\n")
  f.write("</Layer>\n")
  y = y + 1
f.close

f = open('../osm-cycle/contour-layers-c10.include', 'w')
y = 0
for X in glob.glob('/home/osm/shape-resources/srtm/*c10.shp'):
  f.write("<Layer name=\"srtm10"+`y`+"\" status=\"on\" srs=\"+proj=latlong +datum=WGS84\">\n")
  f.write("  <StyleName>contours10</StyleName>\n")
  f.write("  <StyleName>contours-text10</StyleName>\n")
  f.write("  <Datasource>\n")
  f.write("    <Parameter name=\"type\"><![CDATA[shape]]></Parameter>\n")
  f.write('    <Parameter name="file"><![CDATA[' + X + "]]></Parameter>\n")
  f.write("  </Datasource>\n")
  f.write("</Layer>\n")
  y = y + 1
f.close

Take the three files and put their content into the osm.xml style file at whichever point you want the contours to show up - usually just after the coastline shapefiles and add styles (necessary).

Styles

<Style name="contours10">
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <LineSymbolizer stroke="#9cb197" stroke-width="0.5" />
        </Rule>
</Style>
<Style name="contours50">
        <Rule>
                <MaxScaleDenominator>204741</MaxScaleDenominator>
                <MinScaleDenominator>51185</MinScaleDenominator>
                <LineSymbolizer stroke="#9cb197" stroke-width="0.6" />
        </Rule>
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <LineSymbolizer stroke="#747b90" stroke-width="0.6" />
        </Rule>
</Style>
<Style name="contours100">
        <Rule>
                <MaxScaleDenominator>409483</MaxScaleDenominator>
                <MinScaleDenominator>204741</MinScaleDenominator>
                <LineSymbolizer stroke="#9cb197" stroke-width="0.7" />
        </Rule>
        <Rule>
                <MaxScaleDenominator>204741</MaxScaleDenominator>
                <MinScaleDenominator>51185</MinScaleDenominator>
                <LineSymbolizer stroke="#747b90" stroke-width="0.7" />
        </Rule>
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <LineSymbolizer stroke="#855d62" stroke-width="0.7" />
        </Rule>
</Style>
<Style name="contours-text10">
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                
                <TextSymbolizer size="8" fill="#9c9" fontset-name="fontset-0" halo-radius="1" wrap-width="14">[ID]</TextSymbolizer>
                <TextSymbolizer face-name="DejaVu Sans Book" size="8" fill="#747b90" halo-radius="1" placement="line">[height]</TextSymbolizer>
        </Rule>
</Style> 
<Style name="contours-text50">
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <TextSymbolizer size="8" fill="#9c9" fontset-name="fontset-0" halo-radius="1" wrap-width="14">[ID]</TextSymbolizer>
                <TextSymbolizer face-name="DejaVu Sans Book" size="8" fill="#747b90" halo-radius="1" placement="line">[height]</TextSymbolizer>
        </Rule>
</Style>
<Style name="contours-text100">
        <Rule>
                <MaxScaleDenominator>102370</MaxScaleDenominator>
                <MinScaleDenominator>51185</MinScaleDenominator>
                <TextSymbolizer face-name="DejaVu Sans Book" size="8" fill="#747b90" halo-radius="1" placement="line" >[height]</TextSymbolizer>
        </Rule>
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <TextSymbolizer face-name="DejaVu Sans Book" size="8" fill="#855d62" halo-radius="1" placement="line" >[height]</TextSymbolizer>
        </Rule>
</Style>

See also: Tutorial on thematicmapping.org which goes via shapefile

The PostGIS approach

Opm.png

The alternative approach, developed by OpenPisteMap, is to store the contours data in PostGIS. This only requires one set of layers to be set up for the whole data set, rather than one set per shapefile.

Needs CHECKING!

Importing ASTER_GDEMv2 data

This tutorial deals with ASTER Global Digital Elevation Model V002 data from NASA Reverb|ECHO.

Put the .zip archives containing the DEM data in a single directory, make sure you have the GDAL utilities installed (gdal-bin in Debian/Ubuntu) and of course, prepared a postgresql-postgis databse. (if you have no databse yet, you might want to look at the following mapbox-tutorials: OSM-Bright Ubuntu quickstart )

The author does not take any responsibility for the following script. Use on your own risk!

Run the "import_ASTGTM2.sh" script:

#!/bin/bash

##  ======================================================================================== ##
##  file:  import_ASTGTM2.sh                                          Author: ikcalB
##  --------------------------
##  
##  import *ASTER_GDEM v2* into postgis databse
##  (utilize  "man <command>"  if you need help)
##
##                                                      /-  ASTGTM2_N##E###_dem.tif
##  data-STRUCTURE:             ASTGTM2_N##E###.zip     --  ASTGTM2_N##E###_num.tif
##                                                      \-  README.pdf
##
##  (example for Northern hemisphere,                   *FURTHER information in README.pdf*
##   to the east of greenwich)
##  ======================================================================================== ##

PREP_TABLE="1"                          #  initialize table?
TABLE="contours"                        #  table name for insertion
NODATA_VAL="-9999"                      #  ASTER_GDEM v2 nodata-vale 
                                        #+ (according to README.pdf inside each .zip-archive)
INTERVAL="10"                           #  METERS (Resolution for contour-lines) 
COLUMN="height"                         #  column name for height-information
GEOMETRY="geometry"                     #  column name for geometry-information

OPTIONS=(-q -d osm -U postgres)         #  postgresql options: quiet, db-name, username

for FILE in *.zip
do
        # unzip
        unzip "$FILE" -x "*_num.tif" "README.pdf"

        #  sanitize filename for further use
        FILE="${FILE%%.zip}"

        # import contours
        gdal_contour -i $INTERVAL -snodata $NODATA_VAL -a $COLUMN "${FILE}_dem.tif" "${FILE}.shp"

        # prepare database (executed only once)
        (( PREP_TABLE )) && shp2pgsql -p -I -g $GEOMETRY "${FILE}.shp" $TABLE | psql "${OPTIONS[@]}"
        PREP_TABLE=0

        # append data to table
        shp2pgsql -a -g $GEOMETRY "${FILE}.shp" $TABLE | psql "${OPTIONS[@]}"

        # clean up
        rm "${FILE}_dem.tif"                            # extracted ASTER_GDEM file
        rm "${FILE}.shp"                                # computed shape file
        rm "${FILE}.dbf" "${FILE}.shx" "${FILE}.prj"   # shapefile information
done

echo
echo "DONE"

exit 0

This will import all of the contours data for 10m interval contours into the PostGIS-databse osm authenticating as postgres inserting into table contours. (Customization is eased by using variables)

P.S.: README.pdf found in ASTGTM2-files declares "-9999" value for void-data.

Importing the SRTM3 data (deprecated)

Put the zip files from NASA in a single directory, make sure you have the GDAL utilities installed (gdal-bin in Debian/Ubuntu) and srtm_generate_hdr.sh in your path (srtm_generate_hdr.sh is not needed with newer Gdal; adapted script at #Importing ASTER_GDEMv2 data).

Then run this import script:

#!/bin/bash

PREP_TABLE="1"
for X in *.hgt.zip; do
	yes | ./srtm_generate_hdr.sh $X
	rm -f "${X%%.zip}"
	
	# Import 10m contours
	rm -f "${X%%.hgt.zip}.shp" "${X%%.hgt.zip}.shx" "${X%%.hgt.zip}.dbf"
	gdal_contour -i 10 -snodata 32767 -a height "${X%%.hgt.zip}.tif" "${X%%.hgt.zip}.shp"
	[ "$PREP_TABLE" ] && shp2pgsql -p -I -g way "${X%%.hgt.zip}" contours | psql -q gis
	shp2pgsql -a -g way "${X%%.hgt.zip}" contours | psql -q gis
	
	rm -f "${X%%.hgt.zip}.shp" "${X%%.hgt.zip}.shx" "${X%%.hgt.zip}.dbf"
	rm -f "${X%%.hgt.zip}.bil"
	rm -f "${X%%.hgt.zip}.hdr"
	rm -f "${X%%.hgt.zip}.prj"
	rm -f "${X%%.hgt.zip}.tif"
	unset PREP_TABLE
done

This will import all of the contours data for 10m interval contours into a PostGIS table called contours.

PS: Maybe I'm wrong, but to take "no values" (gap) into account, the SRTM nodata is -32767, so I replaced "gdal_contour -i 10 -snodata 32767" by "gdal_contour -i 10 -snodata -32767"
Response: "32767" is correct. NASA says the "no data" value is -32768, but the files appear to actually be using 32767.

Setting up Mapnik

You need to add layers for the contour intervals you are interested in:

<Layer name="srtm_10" status="on" srs="+proj=latlong +datum=WGS84">
        <StyleName>contours10</StyleName>
        <StyleName>contours-text10</StyleName>
        <Datasource>
                <Parameter name="type">postgis</Parameter>
                <Parameter name="host"></Parameter>
                <Parameter name="port"></Parameter>
                <Parameter name="user"></Parameter>
                <Parameter name="password"></Parameter>
                <Parameter name="dbname">gis</Parameter>
                <Parameter name="estimate_extent">false</Parameter>
                <Parameter name="table">(SELECT way,height FROM contours WHERE height::integer % 10 = 0 AND height::integer % 50 != 0 AND height::integer % 100 != 0) AS "contours-10"</Parameter>
                <Parameter name="extent">-180,-89.99,180,89.99</Parameter>
        </Datasource>
</Layer>
<Layer name="srtm_50" status="on" srs="+proj=latlong +datum=WGS84">
        <StyleName>contours50</StyleName>
        <StyleName>contours-text50</StyleName>
        <Datasource>
                <Parameter name="type">postgis</Parameter>
                <Parameter name="host"></Parameter>
                <Parameter name="port"></Parameter>
                <Parameter name="user"></Parameter>
                <Parameter name="password"></Parameter>
                <Parameter name="dbname">gis</Parameter>
                <Parameter name="estimate_extent">false</Parameter>
                <Parameter name="table">(SELECT way,height FROM contours WHERE height::integer % 50 = 0 AND height::integer % 100 != 0) AS "contours-50"</Parameter>
                <Parameter name="extent">-180,-89.99,180,89.99</Parameter>
        </Datasource>
</Layer>
<Layer name="srtm_100" status="on" srs="+proj=latlong +datum=WGS84">
        <StyleName>contours100</StyleName>
        <StyleName>contours-text100</StyleName>
        <Datasource>
                <Parameter name="type">postgis</Parameter>
                <Parameter name="host"></Parameter>
                <Parameter name="port"></Parameter>
                <Parameter name="user"></Parameter>
                <Parameter name="password"></Parameter>
                <Parameter name="dbname">gis</Parameter>
                <Parameter name="estimate_extent">false</Parameter>
                <Parameter name="table">(SELECT way,height FROM contours WHERE height::integer % 100 = 0) AS "contours-100"</Parameter>
                <Parameter name="extent">-180,-89.99,180,89.99</Parameter>
        </Datasource>
</Layer>

And you need some styles:

<Style name="contours10">
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <LineSymbolizer>
                        <CssParameter name="stroke">#9cb197</CssParameter>
                        <CssParameter name="stroke-width">0.5</CssParameter>
                </LineSymbolizer>
        </Rule>
</Style>
<Style name="contours50">
        <Rule>
                <MaxScaleDenominator>204741</MaxScaleDenominator>
                <MinScaleDenominator>51185</MinScaleDenominator>
                <LineSymbolizer>
                        <CssParameter name="stroke">#9cb197</CssParameter>
                        <CssParameter name="stroke-width">0.6</CssParameter>
                </LineSymbolizer>
        </Rule>
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <LineSymbolizer>
                        <CssParameter name="stroke">#747b90</CssParameter>
                        <CssParameter name="stroke-width">0.6</CssParameter>
                </LineSymbolizer>
        </Rule>
</Style>
<Style name="contours100">
        <Rule>
                <MaxScaleDenominator>409483</MaxScaleDenominator>
                <MinScaleDenominator>204741</MinScaleDenominator>
                <LineSymbolizer>
                        <CssParameter name="stroke">#9cb197</CssParameter>
                        <CssParameter name="stroke-width">0.7</CssParameter>
                </LineSymbolizer>
        </Rule>
        <Rule>
                <MaxScaleDenominator>204741</MaxScaleDenominator>
                <MinScaleDenominator>51185</MinScaleDenominator>
                <LineSymbolizer>
                        <CssParameter name="stroke">#747b90</CssParameter>
                        <CssParameter name="stroke-width">0.7</CssParameter>
                </LineSymbolizer>
        </Rule>
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <LineSymbolizer>
                        <CssParameter name="stroke">#855d62</CssParameter>
                        <CssParameter name="stroke-width">0.7</CssParameter>
                </LineSymbolizer>
        </Rule>
</Style>
<Style name="contours-text50">
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <TextSymbolizer name="height" face_name="DejaVu Sans Book" size="8" fill="#747b90" halo_radius="1" placement="line" />
        </Rule>
</Style>
<Style name="contours-text100">
        <Rule>
                <MaxScaleDenominator>102370</MaxScaleDenominator>
                <MinScaleDenominator>51185</MinScaleDenominator>
                <TextSymbolizer name="height" face_name="DejaVu Sans Book" size="8" fill="#747b90" halo_radius="1" placement="line" />
        </Rule>
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <TextSymbolizer name="height" face_name="DejaVu Sans Book" size="8" fill="#855d62" halo_radius="1" placement="line" />
        </Rule>
</Style>

For mapnik version 2.1 the xml changed a little bit, so I added this in osm.xml

<Layer name="srtm_10" status="on" srs="+proj=latlong +datum=WGS84">
        <StyleName>contours10</StyleName>
        <StyleName>contours-text10</StyleName>
        <Datasource>
      &datasource-settings;
                <Parameter name="table">(SELECT way,height,height as name FROM contours WHERE height::integer % 10 = 0 AND height::integer % 50 != 0 AND height::integer % 100 != 0) AS "contours-10"</Parameter>
        </Datasource>
</Layer>
<Layer name="srtm_50" status="on" srs="+proj=latlong +datum=WGS84">
        <StyleName>contours50</StyleName>
        <StyleName>contours-text50</StyleName>
        <Datasource>
      &datasource-settings;
                <Parameter name="table">(SELECT way,height,height as name FROM contours WHERE height::integer % 50 = 0 AND height::integer % 100 != 0) AS "contours-50"</Parameter>
        </Datasource>
</Layer>
<Layer name="srtm_100" status="on" srs="+proj=latlong +datum=WGS84">
        <StyleName>contours100</StyleName>
        <StyleName>contours-text100</StyleName>
        <Datasource>
      &datasource-settings;
                <Parameter name="table">(SELECT way,height,height as name FROM contours WHERE height::integer % 100 = 0) AS "contours-100"</Parameter>
        </Datasource>
</Layer>
<Style name="contours10">
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <LineSymbolizer stroke="#9cb197" stroke-width="0.5" />
        </Rule>
</Style>
<Style name="contours50">
        <Rule>
                <MaxScaleDenominator>204741</MaxScaleDenominator>
                <MinScaleDenominator>51185</MinScaleDenominator>
                <LineSymbolizer stroke="#9cb197" stroke-width="0.6" />
        </Rule>
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <LineSymbolizer stroke="#747b90" stroke-width="0.6" />
        </Rule>
</Style>
<Style name="contours100">
        <Rule>
                <MaxScaleDenominator>409483</MaxScaleDenominator>
                <MinScaleDenominator>204741</MinScaleDenominator>
                <LineSymbolizer stroke="#9cb197" stroke-width="0.7" />
        </Rule>
        <Rule>
                <MaxScaleDenominator>204741</MaxScaleDenominator>
                <MinScaleDenominator>51185</MinScaleDenominator>
                <LineSymbolizer stroke="#747b90" stroke-width="0.7" />
        </Rule>
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <LineSymbolizer stroke="#855d62" stroke-width="0.7" />
        </Rule>
</Style>
<Style name="contours-text50">
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <TextSymbolizer size="8" fill="#9c9" fontset-name="bold-fonts" halo-radius="1" wrap-width="14">[name]</TextSymbolizer>
                <TextSymbolizer face-name="DejaVu Sans Book" size="8" fill="#747b90" halo-radius="1" placement="line">[height]</TextSymbolizer>
        </Rule>
</Style>
<Style name="contours-text100">
        <Rule>
                <MaxScaleDenominator>102370</MaxScaleDenominator>
                <MinScaleDenominator>51185</MinScaleDenominator>
                <TextSymbolizer face-name="DejaVu Sans Book" size="8" fill="#747b90" halo-radius="1" placement="line" >[height]</TextSymbolizer>
        </Rule>
        <Rule>
                <MaxScaleDenominator>51185</MaxScaleDenominator>
                <MinScaleDenominator>1599</MinScaleDenominator>
                <TextSymbolizer face-name="DejaVu Sans Book" size="8" fill="#855d62" halo-radius="1" placement="line" >[height]</TextSymbolizer>
        </Rule>
</Style>

Other considerations

Colours, styles etc

If you are using the shapefiles approach, one thing to consider when you are coming up with your styles is that the 100m contours will be rendered 3 times over - the 100m contours are in the 10, 50 and 100m shapefiles. So it's a good idea to make them slightly wider than the 50s, which are slightly wider than the 10s. It doesn't matter if they are all the same colour, but when they are different colours they will go muddy due to anti-aliasing. The PostGIS approach avoids this problem since the select statements in the layers will exclude overlapping contours (i.e. the 10m layer won't contain contours at 50m and 100m heights and the 50m layer won't contain contours at 100m heights).

Void filling

Although the voids aren't rendered, they still exist as gaps in the contours. Although single-pixel gaps and other small voids could be filled by extrapolation, there are larger areas that need other sources of data.

Other sources

Given the voids, and the lattitude limits on the data, future work will include using the lower-resolution SRTM30 dataset to fill in the gaps.

Tools

See Srtm2Osm to convert srtm data to osm files.

Related

HikingBikingMaps shows how to render images with hill shading in addition to contour lines.

Storage requirements

Here are some numbers from the mailinglist on disk space usage for those interested. I had a go at importing 10 metre contour lines for the whole of Eurasia into PostGIS - latitudes of 0 - 46 degrees North required about 110 gig of disk space for the Postgres table and amounted to around 105 million contour lines. (At this point before I ran out of space) - the SRTM3 data set extends up to 60 degrees North).

Between 0 and 46 degrees north across Eurasia amounts to 3244 1x1 degree tiles. So this averages you around 35MB of disk space to import a 1x1 degree tile into PostGIS (obviously dependent on the terrain the tile covers), giving rough estimated numbers of:

Location est. storage req. number of tiles
Africa 111 GB (3250 tiles)
Australia 36 GB (1060 tiles)
Eurasia 202 GB (5902 tiles)
Islands 5 GB (141 tiles)
N America 82 GB (2412 tiles)
S America 62 GB (1807 tiles)
WHOLE WORLD 498 GB (14572 tiles)

So with half a terabyte of disk you can import the whole lot... There is also the higher resolution SRTM1 data set covering North America - I'm not clear on how using those data would affect these numbers - probably substantially, since the number of vertices in each contour line would go up due to the higher resolution of the DEM