This tutorial will ensure that Mapnik and its Python bindings are properly installed and introduce you to some of the basic programming concepts for Mapnik.
Make sure you have mapnik installed. You should be able to open a terminal and type:
mapnik-config -v # should return a version number.
This tutorial expects Mapnik 2.x or greater. Older versions do not provide the
mapnik-config program, so we recommend upgrading.
Please note that if you are using mapnik 2.0.0 then you will need to adjust the "mapnik" module name to "mapnik2" in the Python commands. Refer to mapnik2 for details of the naming convention.
Please also note that unlike Mapnik 2.x, version 3.x does not include Python bindings anymore. You can find the new Python bindings here.
Next test the Python bindings. You should be able to open a terminal and type:
python -c "import mapnik;print mapnik.__file__" # should return the path to the python bindings and no errors
If the above does not work (e.g. throws an
ImportError) then please go back and ensure Mapnik is properly installed. If you need help, sign up for the mailing list to ask questions or join the #mapnik room on freenode IRC
Now, we need some data to render. Let's use a shapefile of world border polygons from http://naturalearthdata.com. Download the data from this wiki's local cache here or directly from the Natural Earth Data site. Unzip the archive in an easily accessible location of your choosing. In Step 3 we will be referencing the path to this shapefile in Python code, so make sure you know where you put it.
Once unzipped, you should see four files like:
ne_110m_admin_0_countries.shp ne_110m_admin_0_countries.shx ne_110m_admin_0_countries.dbf ne_110m_admin_0_countries.prj
To download and unzip on the command line with the do:
wget https://github.com/mapnik/mapnik/wiki/data/110m-admin-0-countries.zip unzip 110m-admin-0-countries.zip # creates ne_110m_admin_0_countries.shp
Now we're going to program in Python and Mapnik, using sample code and the Python interpreter.
The idea here is not that you have to interact with Mapnik via Python, but that this is a good way to build foundational skills for how Mapnik works.
So, let's begin! Open a Python interpreter simply by typing in your terminal:
The code below can be pasted into your interpreter. Ideally paste line by line so you can confirm each step is working. The commented lines (#) should be able to be pasted without trouble, but depending on your interpreter setting may cause errors.
Import the Mapnik Python bindings:
m = mapnik.Map(600,300) # create a map with a given width and height in pixels # note: m.srs will default to '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' # the 'map.srs' is the target projection of the map and can be whatever you wish m.background = mapnik.Color('steelblue') # set background colour to 'steelblue'.
Create the Styles which determines how the data is rendered:
s = mapnik.Style() # style object to hold rules r = mapnik.Rule() # rule object to hold symbolizers # to fill a polygon we create a PolygonSymbolizer polygon_symbolizer = mapnik.PolygonSymbolizer(mapnik.Color('#f2eff9')) r.symbols.append(polygon_symbolizer) # add the symbolizer to the rule object # to add outlines to a polygon we create a LineSymbolizer line_symbolizer = mapnik.LineSymbolizer(mapnik.Color('rgb(50%,50%,50%)'),0.1) r.symbols.append(line_symbolizer) # add the symbolizer to the rule object s.rules.append(r) # now add the rule to the style and we're done
And add the Style to the Map:
m.append_style('My Style',s) # Styles are given names only as they are applied to the map
In Step 2 above you should have downloaded a sample shapefile of polygons of world countries. We are now going to load that into a
mapnik.Datasource object in Python.
If your Python interpreter was launched from the same directory as you downloaded the natural earth shapefile to you should be able to use a relative path to create the datasource like:
ds = mapnik.Shapefile(file='ne_110m_admin_0_countries.shp')
Otherwise use an absolute path (exchanging
/Users/dane/Downloads/ for the correct path on your machine):
ds = mapnik.Shapefile(file='/Users/dane/Downloads/ne_110m_admin_0_countries.shp')
Note: optionally (to learn about your data) you can call the
envelope() function off the datasource object to see the full coordinate bounds of the data:
>>> ds.envelope() Box2d(-180.0,-90.0,180.0,83.64513)
That shows the minx, miny, maxx, and maxy of the data. Because the above coordinates are between -180 and 180 for the x or longitude values and -90 and 90 for the y or latitude values we know this data is in geographic coordinates and uses degrees for units - a pretty good indication this is
WGS84 (aka EPSG:4326). This specific shapefile also stores this projection information as a
WKT string in the
ne_110m_admin_0_countries.prj file. See the
layer.srs value below for why this matters.
Mapnik Layers are basically containers around datasources, that store useful properties. Lets now create a Layer object and add the datasource to it.
layer = mapnik.Layer('world') # new layer called 'world' (we could name it anything) # note: layer.srs will default to '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'
Note: the 'layer.srs' is the source projection of the Datasource and must match the projection of the coordinates of that data or else your map will likely be blank. Mapnik uses Proj.4 strings to specify the spatial references system. In this case, the default
srs Mapnik assumes (
+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs) happens to match the projection of the data. When this is not the case you must set the layer.srs to the correct value (which is beyond the scope of this tutorial).
Now attach the datasource to the layer, and reference:
layer.datasource = ds
Lastly, we need to make sure the style we created above (and attached to the map) is also applied to the layer, by its string reference:
This step is critical. Finally add the layer to the map and zoom to the full extent of the data layer (using
zoom_all which will calculate the cumulative extent of all layers attached to the map). If you do not zoom the Map to the extent of the layer(s), then the rendered output will be blank.
Finish up by rendering your map image:
# Write the data to a png image called world.png the current directory mapnik.render_to_file(m,'world.png', 'png') # Exit the Python interpreter exit() # or ctrl-d
Then back in your normal shell type:
# On a mac open world.png # On windows start world.png
Or navigate to your base directory and open
world.png and the result should look like this: ![world.png]
The next logical step is to run that same code all at once as a Python script from your shell/terminal (rather than pasted into the Python interpreter line-by-line). This way you will be able to modify and experiment with the settings, then simply re-run the script.
So, create a blank text file called
Make it executable:
chmod +x world.py
Then add a line at the top of the script like:
Finally, append the entire text below and save the file.
import mapnik m = mapnik.Map(600,300) m.background = mapnik.Color('steelblue') s = mapnik.Style() r = mapnik.Rule() polygon_symbolizer = mapnik.PolygonSymbolizer(mapnik.Color('#f2eff9')) r.symbols.append(polygon_symbolizer) line_symbolizer = mapnik.LineSymbolizer(mapnik.Color('rgb(50%,50%,50%)'),0.1) r.symbols.append(line_symbolizer) s.rules.append(r) m.append_style('My Style',s) ds = mapnik.Shapefile(file='ne_110m_admin_0_countries.shp') layer = mapnik.Layer('world') layer.datasource = ds layer.styles.append('My Style') m.layers.append(layer) m.zoom_all() mapnik.render_to_file(m,'world.png', 'png') print "rendered image to 'world.png'"
Finally run the script with the command:
./world.py # You must be in the same directory as you saved the script
srsif you change the datasource).