Making your map look good
Now that we have mastered mapnik
, here are some other things that you can do to make sure that your maps look good.
Projection
You should always project your map before you draw it. you can do this simply, by setting the srs
attribute of your Map
object to the required Proj4 string.
Proj4 strings can be obtained from spatialreference.org.
# set the map to British National Grid
m.srs = '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs'
North Arrow
Adding a North arrow to your map is actually quite simple, and most of you achieved this in your assessments: well done! All that you have to do is:
- Open your map in
PIL
- Open a North Arrow image in
PIL
(millions are available on Google…) - Paste the North Arrow onto the map using
PIL
- Save the resulting image using
PIL
Simple, right? The only thing to bear in mind is that we are dealing with images, so all coordinates for positioning things in PIL
are in Image Space (0,0 in the top left corner…). Aside from that, it’s dead easy!
For example, the following will add a north arrow to the map for you:
from PIL import Image
...
# render to file and open in PIL
mapnik.render_to_file(m, 'output/map.png', 'png')
mapImg = Image.open('output/map.png')
# open and resize north arrow (size in pixels)
northArrow = Image.open('data/north.png').convert('RGBA').resize((50,50), Image.ANTIALIAS)
# paste onto the map (coordinates for Top Left corner in Image Space) (using itself as a mask to maintain transparency)
mapImg.paste(northArrow, (5, 5), northArrow)
# save the image (with scalebar) to the map
mapImg.save('output/map.png', "PNG")
As you can see, the above process is:
- Open the map
- Open the North Arrow and resize it to 50px2 (using an antialias smoothing filter to stop it pixellating). You are also making sure that the colour profile is RGBA (Red, Green, Blue, Alpha), where Alpha refers to a fourth band of data denoting transparency. This means you don’t have to have a white square around your arrow!
- Paste the north arrow onto the map with its top left corner 5 pixels to the right and 5 pixels down from the top left of the map image. The third argument (
northArrow
again) prevents is used as a mask and has the effect of making the background of the image transparent (posssible because of the Alpha band). - Overwrite the original map image with the new map, North Arrow included!
Give it a go! Add a North arrow to your map
Map Attribution
PIL
does not distinguish drawing text onto the map from drawing a line, circle or whatever. As such, attribution text (as with all drawing in PIL
) is done using part of PIL
called ImageDraw
, which contains all of the functions that you could ever want for this purpose.
The first step is simply to get a drawing context for our map image:
from PIL import Image, ImageDraw, ImageFont
...
# render mapnik map to file
mapnik.render_to_file(m, 'output/map.png', 'png')
# open that file in PIL
mapImg = Image.open('output/map.png')
# get PIL context to draw on
draw = ImageDraw.Draw(mapImg)
Next, you need to work out what you are going to say, and then work out the size of that much text using draw.textsize()
:
# text to write
attribution = 'Data Copyright Ordnance Survey OpenData'
# get the dimensions of the text
tw, th = draw.textsize(attribution)
Next, define a font (the default one is pretty bad…). Fonts are stored in .ttf files, and you can use any that you like (there are millions available online). The best place to find them at teh moment is probably Google Fonts. Select one that you like, click Select this Font and then in the little box that appears at the bottom of the screen you can select to Download the font (top right button):
Once downloaded, unzip the file and put the .ttf file (or files if you want several…) in your project directory. Remember to check your filepath when you use it!
# prepare a font
font = ImageFont.truetype('data/open-sans/OpenSans-Regular.ttf', 12)
And then finally, it is time to add your text to the map. In the below case:
- 10px above and to the left of the bottom right-hand corner
- the text as defined in the
attribution
variable - the text will be drawn black (
(0,0,0)
) - the font as defined above
# add attribution to the map
draw.text((m.width-20, m.height-20), attribution, fill=(0,0,0), font=font)
Give it a go! Add data attribution to your map
Scale Bar
Adding a scalebar is a little more complicated. However (as some of you discovered in your assessment), I have created a small library to allow you to add scalebars to mapnik
maps.
You can download the library here, and you can you can read a description of it here. Simply unzip the resulting folder and drag all of the contents into your project directory (so that scalebar.py ends up next to your current .py file).
This function is designed to be a quick and dirty approach to scalebars, it works out how long it should be based upon the size of the map, and draws itself to that size, you can’t customise it with a load of settings (but you can edit scalebar.py if you would like to make changes yourself…).
To use it, just import scalebar
and then pass your Map
object (m
) to the scalebar.getScaleBar(m)
. It will give you back a PIL
image object containing the scalebar, which you just paste onto your map wherever you want it. Simple!
For example:
from PIL import Image
import scalebar
...
# render mapnik map to file
mapnik.render_to_file(m, 'map.png', 'png')
# open rendered map for editing
im = Image.open('output/map.png')
# add scalebar
sb = scalebar.getScaleBar(m)
im.paste(sb, (10, m.height-10-th))
# save output
im.save('output/map.png', 'png')
Give it a go! Add a Scale Bar to your map. If you’re feeling fancy, edit it a bit, change the font, see what happens!
Legend, Title, etc… (optional extra)
You get the idea by now - if you want to add anything else to your map, you simply have to draw it using PIL
! The examples above (including the code in scalebar.py
) give you everything that you need to be able to manage a legend, title, and anything else that you can think of!