How to: database-less local map rendering with Mapnik and OSM

  |   Source

Here's a quick howto for local map rendering, using python, mapnik and data from OpenStreetMap.

This tutorial shows how to create a stylesheet for mapnik from scratch, if you're looking for how to tweak OpenStreetMap's mapnik rendering, I plan to write a tutorial on PostGIS-driven rendering at a later time.

*This post is not for those who want to make a local slippy map, nor those who want to render different areas of big dumps (think of big national dumps).*

We'll render some features of a residential area of a city with buildings mapped. We'll get a small dump of the area for this, using the XAPI.

First, you need to install mapnik. On Debian-like systems, it should be as easy as:

# apt-get install python-mapnik

Then, you need some python code to use the mapnik library. Let's call it render.py:

#!/usr/bin/python
import mapnik

# Configuration
style = 'style.xml'
output = 'output.svg'
width = 1280
height = 800
bbox = '12.58664,37.65759,12.5945,37.66325'

#Don't touch below!
bbox = bbox.split(',')
ll = mapnik.Coord(float(bbox[0]), float(bbox[1]))
tr = mapnik.Coord(float(bbox[2]), float(bbox[3]))

mymap = mapnik.Map(width, height)
mapnik.load_map(mymap, style)

map_bbox = mapnik.Envelope(ll, tr)
mymap.zoom_to_box(map_bbox)
mapnik.render_to_file(mymap, output)

For the purposes of this post, we're going to create a 1280x800 map -- adjust that to suit your needs -- and we specified a bounding box corresponding to that area. That's composed as west,south,east,north coordinates. Be sure to get them right :-).

Now we need to write a Mapnik stylesheet, we'll call it style.xml. That's a XML document, you can find the documentation at the official website. The skeleton of a stylesheet is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Map>
<Map bgcolor="lightblue" srs="+proj=latlong +datum=WGS84">
    <Style name="...">
    ...
    </Style>
    <Layer name="...">
        <Datasource>
        ...
        </Datasource>
    </Layer>
</Map>

The stylesheet has an arbitrary number of `Style <http://trac.mapnik.org/wiki/XMLConfigReference#Style>`__ elements and an arbitrary number of `Layer <http://trac.mapnik.org/wiki/XMLConfigReference#Layer>`__ elements. The former class of elements define the appearance of the features, while the latter defines where to get data from. Each layer has a `Datasource <http://trac.mapnik.org/wiki/XMLConfigReference#Datasource>`__ child: here's where you say where to get your data from. We're going to use the osm input plugin, which lets you parse the dump directly. This has some drawbacks:

  • not scalable for big dumps
  • for each bbox you want to render, the full dump has to be re-parsed

But works great for small dumps.

In this tutorial, we want to pretty render a residential area, so we first need to render the road network. This will mainly be made of highway=residential roads.

Each style can have one or more Rule element. These define what elements that particular style should apply to. Each rule can have different rendering settings.

The given dump has different roads: residential, unclassified, tertiary and secondary. Let's add a rule for each. Here's the first of them:

<Rule>
    <Filter>[highway] = 'residential'</Filter>
    <LineSymbolizer>
        <CssParameter name="stroke">#ffffff</CssParameter>
        <CssParameter name="stroke-width">5</CssParameter>
        <CssParameter name="stroke-linejoin">round</CssParameter>
        <CssParameter name="stroke-linecap">round</CssParameter>
    </LineSymbolizer>
    <TextSymbolizer name="name" face_name="DejaVu Sans Book" size="8" fill="#000" halo_radius="1" spacing="300" placement="line" />
</Rule>

It is quite straightforward: you define what elements the rule should apply to, and then add some symbolizer. For a detailed description of each symbolizer, please read the documentation.

The example dump also has a railway and some steps. To render them, we use the stroke-dasharray property:

<Rule>
    <Filter>[highway] = 'steps'</Filter>
    <LineSymbolizer>
        <CssParameter name="stroke">#ff0000</CssParameter>
        <CssParameter name="stroke-width">5.0</CssParameter>
        <CssParameter name="stroke-dasharray">2,1</CssParameter>
    </LineSymbolizer>
</Rule>
<Rule>
    <Filter>[railway] = 'rail'</Filter>
    <LineSymbolizer>
        <CssParameter name="stroke">#222222</CssParameter>
        <CssParameter name="stroke-width">3</CssParameter>
        <CssParameter name="stroke-linejoin">round</CssParameter>
    </LineSymbolizer>
    <LineSymbolizer>
        <CssParameter name="stroke">white</CssParameter>
        <CssParameter name="stroke-width">1</CssParameter>
        <CssParameter name="stroke-linejoin">round</CssParameter>
        <CssParameter name="stroke-dasharray">10,20</CssParameter>
    </LineSymbolizer>
</Rule>

Also here, it is quite simple. The values given to stroke-dasharray determine the size of the dashes, and they can be alternated. So, for the steps we have 2px of #ff0000 and 1px of trasparency, while for the rail we have 10px of white and 20px of #222222.

What if we want to add some markers to the map? Easy, just use another symbolizer, called `PointSymbolizer <http://trac.mapnik.org/wiki/PointSymbolizer>`__:

<Rule>
    <Filter>[highway] = 'stop'</Filter>
    <PointSymbolizer width='10' height='22' file='stop.png' type='png' allow_overlap='true' opacity='1.0' />
</Rule>

Here we mark stop signals with a pre-rendered stop.png (only png and tiff are currently supported, and svg support is only in the development version).

What to do next? Well, we can make this map really nice. We'll render buildings with a pseudo-3D effect, which, however, does not reflect real heights -- but it's rather nice to see. How to achieve this? BuildingSymbolizer is the answer.

<Rule>
    <Filter>[building] &amp;lt;&amp;gt; ''</Filter>
    <BuildingSymbolizer>
        <CssParameter name="fill">#00ff00</CssParameter>
        <CssParameter name="fill-opacity">0.4</CssParameter>
        <CssParameter name="height">0.00007</CssParameter>
    </BuildingSymbolizer>
</Rule>

Here we just draw building=*. Regarding the height, you need to play a bit -- it took a while before I found a good value of it.

One thing missing in this stylesheet, is the rendering of coastlines. Since we don't have a coastline here, I'm skipping it, but the easy way is: use another rule. This is really just a trick; OpenStreetMap renders the coastline in a different way respect to other features, that's why I'm using this trick.

I'm not posting the whole stylesheet, but you can download it. You can also get the script, the OSM dump and the stop.png image.

Now, you should be able to just do:

$ ./render.py

and you will have your map in output.svg :-)

Here's what you would get if you use the files provided above (click to see the original SVG version):

/data/dbless-local-mapnik-osm/output.png
Comments powered by Disqus
Contents © 2013 David Paleino - Powered by Nikola