Mapnik/Rendering OSM XML data directly
So, you wonder, how to render your own map with Mapnik? Here, you go... This tutorial covers the basic steps starting from scratch. First of all, make sure you have Mapnik installed. See the Mapnik Installation Guide on Mapnik's GitHub for this.
This example does not use a postgresql database but takes the data directly from a .osm XML file - this has some limitations but allows you to get started more quickly.
We will render a map of a small area of southern Bavaria, Germany. Everything has been tested under Arch Linux and Ubuntu but should in principle work on any UNIX operating system with a bash, Mapnik and Python installed. For other operating systems, you will have to figure out what to modify.
Get the data
You have Mapnik installed and a running python installation on your computer. The next step is to gather the data from OSM. You can use the API to download the data for a small bounding box but for this example, we will download data from www.geofabrik.de. We use the file  containing the region Swabia in Bavaria, Germany:
# cd workdir # wget http://download.geofabrik.de/europe/germany/bayern/schwaben-latest.osm.bz2 # bunzip schwaben-latest.osm.bz2
Writing a style file
Next, we need to create the main xml file which contains instructions for Mapnik about the contents of the map and how to render them. Create the file mapnik_style.xml with the following content:
Syntax for Mapnik 2.2 and above:
<?xml version="1.0" encoding="utf-8"?> <Map background-color="#f2efe9" srs="+proj=latlong +datum=WGS84"> <FontSet name="book-fonts"> <Font face-name="DejaVu Sans Book" /> </FontSet> <Style name="highways"> <Rule> <Filter>[highway] <>''</Filter> <LineSymbolizer stroke="#808080" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" /> </Rule> <Rule> <Filter>[highway] <>''</Filter> <TextSymbolizer name="[name]" fontset-name="book-fonts" size="9" fill="#0000" halo-radius="1" placement="line" /> </Rule> </Style> <Layer name="highways" status="on" srs="+proj=latlong +datum=WGS84"> <StyleName>highways</StyleName> <Datasource> <Parameter name="type">osm</Parameter> <Parameter name="file">schwaben-latest.osm</Parameter> </Datasource> </Layer> </Map>
<?xml version="1.0" encoding="utf-8"?> <Map bgcolor="#f2efe9" srs="+proj=latlong +datum=WGS84"> <FontSet name="book-fonts"> <Font face-name="DejaVu Sans Book" /> </FontSet> <Style name="highways"> <Rule> <Filter>[highway] <> ''</Filter> <LineSymbolizer> <CssParameter name="stroke">#808080</CssParameter> <CssParameter name="stroke-width">2</CssParameter> <CssParameter name="stroke-linejoin">round</CssParameter> <CssParameter name="stroke-linecap">round</CssParameter> </LineSymbolizer> </Rule> <Rule> <Filter>[highway] <> ''</Filter> <TextSymbolizer name="name" fontset_name="book-fonts" size="9" fill="#000" halo_radius="1" placement="line" /> </Rule> </Style> <Layer name="highways" status="on" srs="+proj=latlong +datum=WGS84"> <StyleName>highways</StyleName> <Datasource> <Parameter name="type">osm</Parameter> <Parameter name="file">schwaben-latest.osm</Parameter> </Datasource> </Layer> </Map>
Let's break it down a little:
- Mapnik will create a map with a light beige background, in Spherical Mercator projection (specified by the srs parameter).
- The FontSet element specifies the fonts we are going to use for the map, giving each a symbolic name. Our example uses only one font.
- The highways style tells Mapnik how to render roads:
- The Filter element in each rule specifies which objects to apply the rendering rule to. Here, we selected all roads, i.e. all elements with a non-empty highway tag. Any tag can be used for filtering, Boolean expressions are also possible.
- Roads are rendered as a gray line, 2 pixels wide.
- A text label is placed on top of each road. The text label contains the value of the "name" tag, using the font we specified before, at 9 points, and with a white halo, 1 pixel wide, around the letters.
- The Layer element tells Mapnik where the data comes from: an OSM file named schwaben.osm. It also specifies that its contents should be rendered as defined in the highways style. Rather than using a local OSM file, we can also provide a URL (API, XAPI or TRAPI) and a bounding box, causing Mapnik to get the data directly from a server. Set the srs parameter as shown in this example.
You can add more rules and filters to define all the map elements you like to have.
Rendering the map
Finally, we have to tell Mapnik to render the map using the style file. We use the Python API of Mapnik to do that. Create a python script render.py with the following content and run it. Depending on your operating system, it might be necessary to turn on the executable bit of this file.
#!/usr/bin/env python2 from mapnik import * mapfile = 'mapnik_style.xml' map_output = 'mymap.png' m = Map(4*1024,4*1024) load_map(m, mapfile) bbox=(Envelope( 10.0,47.5,11.1,48.1 )) m.zoom_to_box(bbox) print "Scale = " , m.scale() render_to_file(m, map_output)
This script tells Mapnik to render mymap.png with a size of 4096x4096 pixels from the style file Mapnik_style.xml. The map will contain the area of the data in the (spherical) rectangle from 47.5° N 10.0° E to 48.1° N 11.1° E. Note that the units taken by the Envelope constructor match those described by the srs part in the mapnik_style.xml file, i.e. srs="+proj=longlat" in this case. The script will also print the scale of the map and create a png file containing the map.
If you want an even simpler approach, you can use Mapnik Viewer: all you need is the map file and the OSM file. Start the viewer and open the map file - it will automatically center and zoom in on the area which holds data. You can zoom in and out, pan around, switch individual layers on and off or modify the map file and refresh the view.
On Ubuntu, just install the mapnik-viewer package. On other OSes, look for a similar package - if it's not included, you can find build instructions on the Mapnik wiki.
Now try modifying the map a little: Replace the LineSymbolizer element with the following:
<LineSymbolizer> <CssParameter name="stroke">black</CssParameter> <CssParameter name="stroke-width">4</CssParameter> <CssParameter name="stroke-opacity">1.0</CssParameter> </LineSymbolizer> <LineSymbolizer> <CssParameter name="stroke">red</CssParameter> <CssParameter name="stroke-width">3.0</CssParameter> </LineSymbolizer>
This will draw a black line with width 4 and on top of it a red line with width 3, resulting in a framed red road.
While this approach is good for quick ad-hoc rendering of a map or to test out new styles, it has some limitations:
- Each layer will take all ways and nodes in the OSM file and render them with the styles defined in it - this is different from using a database as a data source (where you can use a query to get only a subset of the data). You must filter out the objects by defining a Filter element for each of your styles - keep that in mind when copying styles from a map file designed for a different kind of data source.
- Styles will be applied to both nodes and ways as long as the Filter matches - there is no way to distinguish one from the other. It is, however, possible to distinguish areas from ways by checking for the area tag or tags which imply an area.
- Relations in the OSM file will be ignored. Information contained in relations (route, multipolygon) cannot be rendered with this method.
- There is no way to postprocess data on the fly (e.g. get the centroid of an area, auto-correct tags under certain conditions, sort related objects and render only the first) - such advanced processing is done in the data source and requires a database.
- While you can use OSM files downloaded with JOSM for that purpose, objects with changes that have not been uploaded will not show up correctly in the map.