User:Moresby/Understanding Mapnik/Plotting points
The first actual content we will put on our map is a collection of points. Here is the program do to that:
#!/usr/bin/python
# Load the Python mapnik libraries.
import mapnik
# Create a new map.
m = mapnik.Map(480, 320)
# Set the background colour.
m.background = mapnik.Color('ghostwhite')
# Create a point symbolizer.
point_symbolizer = mapnik.PointSymbolizer()
# Create a new rule and add the symbolizer.
r = mapnik.Rule()
r.symbols.append(point_symbolizer)
# Create a new style and add the rule.
s = mapnik.Style()
s.rules.append(r)
# Add the style to the map.
m.append_style('basic_style', s)
# Specify that our data is coming from a CSV file called "data-places.csv".
ds = mapnik.CSV(file='data-places.csv')
# Create a new layer for the map, called "main_map" and add the data
# source and style to that layer.
l = mapnik.Layer('main_map')
l.datasource = ds
l.styles.append('basic_style')
# Add the layer to the map.
m.layers.append(l)
# Zoom to the part of the map we are interested in.
m.zoom_to_box(mapnik.Box2d(0, 0, 480, 320))
# Save the map as a PNG image.
mapnik.render_to_file(m, '020-points.png', 'png')
- For a relatively simple task of plotting a few points, this program looks quite complicated. However, it's fairly straightforward once you understand a few Mapnik basics. The following diagram shows the important parts and how they fit together:
+-------+ | | +---------+ | |---- .background = ----| Color | | | +---------+ | | | | +-------+ +------+ +-----------------+ | |---- append_style() ----| Style |---- .rules.append() ----| Rule |---- .symbols.append() ----| PointSymbolizer | | Map | | +-------+ +------+ +-----------------+ | | /------------\ | | | style-name | | | \------------/ | | | | +-------+ +-----+ | | | |---- .datasource = ----| CSV | | | | | +-----+ | |---- layers.append() ----| Layer | | | | | +-------+ | |---- .styles.append() +-------+ | /------------\ | style-name | \------------/
- The box on the left of the diagram represents our Map object. In our program we create that on line seven by calling
mapnik.Map(480, 320)
, and assign it to variablem
. We've already seen how we set the background colour of the map by creating a Color object and assigning it to the map'sbackground
attribute. This is shown at the top of the diagram. - In order to represent geometry items (points, lines, polygons) on a map, Mapnik uses a variety of objects called symbolizers. When given an item, it's the symbolizer's job to work out how that item should be shown on the map and to draw it in the correct place. We're interested in points, so we choose to use a PointSymbolizer. We create one at line thirteen with
point_symbolizer = mapnik.PointSymbolizer()
. Later we will see some of the ways in which we can alter the behaviours of the PointSymbolizer object, but for now, the defaults will do just fine. - Looking at our diagram, we see that we can't simply connect the PointSymbolizer to the map, so we have to create some intermediate objects. The first of these is a Rule object, which we create at line sixteen with
r = mapnik.Rule()
. We will see later that a Mapnik rule is a way of working out which objects should be shown on the map, and which should be ignored. For now, the default rule of "always try to draw it on the map" will be fine. Each Rule object has a list of symbolizers which it calls when a geometry object should be drawn, which is kept in thesymbols
attribute of the rule. Here we use Python'sappend
method to add our new symbolizer to the list. - To connect our new rule to the map, we need one more item: a Style object. We will see later that a Mapnik style represents a collection of rules which together produce a single layer of our map. For now, the defaults are acceptable, so at line twenty we create a new Style object and at line 21 we append our new rule to its list.
- Our new style should be just what we need to represent our data points on the map. We now need to add it to the map object so that it's available to use when the data is loaded. We do that by calling the
append_style
method of the map object, and giving the style a name. The name we choose doesn't matter all that much, but we will use the same name again later to refer to this style. Here we choose the name'basic_style'
, and register the style with the map object in line 24 asm.append_style('basic_style', s)
. - So now we have set up how we want our data to be shown on the map, we have to work out what data Mapnik should represent. Mapnik can process data from a range of different sources, but for our purposes, the simplest is to put some data in a comma-separated values (CSV) file. The ability for Mapnik to read CSV files was added in release 2.2.0 in June 2013. You can check the version of Mapnik which you have by running the following command:
mapnik-config -v
- If your version number is less than 2.2.0, now may be the time to find a newer version.
- The details of the data file is covered below. For the program, all that is necessary is that we create a new CSV datasource and tell Mapnik where to find the data. This is done in line 27:
ds = mapnik.CSV(file='data-places.csv')
- Mapnik builds maps out of layers, with each layer being drawn on top of the ones below it. We will see later how different layers can be used, but for now we want just one layer, and, again, the default settings are fine. We create the new layer and assign it to variable
l
at line 31 withl = mapnik.Layer('main_map')
. We then have to specify two things about the layer: firstly, we tell it that the source of the data to be plotted is our CSV file datasource at line 32 and then we tell it the style we want to use at line 33. Finally at line 36 we add our new layer to the map, using the style name'basic_style'
which we registered earlier. - We have now configured the map object to specify what data we want plotted and how we want that data to be represented. We are now ready to start generating the image output we want.Firstly we need to choose which part of the map we want. For our purposes, we are going to select the part of the map with x-coordinates between 0 and 480 and y coordinates between 0 and 320. Both the map image size (set at line seven) and the data (from the CSV file) have been chosen to make this work very simply. We use the Mapnik method
zoom_to_box
to specify this area. This method takes one argument: a Mapnik Box2d object representing the area we are interested in. Life before, we could create the object, assign it to a Python variable and pass the variable as the argument to this method. But here we choose to do it all at once at line 39:m.zoom_to_box(mapnik.Box2d(0, 0, 480, 320))
- Line 42 now finally creates and saves the image file. We save it to a PNG file called
020-points.png
.
We also need to have some data to plot. The following data are some coordinates for some towns and cities in the United Kingdom. For now, don't worry too much about what the coordinates mean, just that they represent the locations on the map we want to create. Save the following lines in a file called data-places.csv
:
name, type, x, y
"Bedford", town, 48, 93
"Bury St Edmunds", town, 398, 147
"Cambridge", city, 221, 125
"Ely", city, 263, 222
"Harlow", town, 220, -81
"Huntingdon", town, 131, 188
"Kettering", town, -29, 222
"Letchworth", town, 119, 17
"March", town, 213, 297
"Newmarket", town, 307, 146
"Peterborough", city, 113, 308
"Royston", town, 179, 51
"Saffron Walden", town, 259, 39
"Sherington", town, -22, 79
"St Ives", town, 164, 190
"St Neots", town, 108, 133
"Stowmarket", town, 483, 119
"Thetford", town, 409, 231
- This file is a simple comma-separated variable (CSV) file.
- The first line specifies what data is provided in the file. In this case it tells us to expect lines which each contain four values, to be labelled "name", "type", "x" and "y". We can choose what we call each of our data fields, but a few names are interpreted in special ways by Mapnik. The field names "x" and "y" are two of these, and Mapnik will interpret these values as the x coordinate and y coordinate of each of our locations.
- The second line is blank. This is ignored by Mapnik.
- The remaining lines each represent one town or city, and contains the following fields, separated by commas:
- the name of the location
- a single value describing the location type:
town
orcity
- the x coordinate of the location
- the y coordinate of the location
- The columns have been spaced to line up neatly to make the data more readable, but this isn't necessary: Mapnik splits each line where it finds a comma, and then tidies up any whitespace around it.
- The town/city names have been put in double quotes. For our data, this is not actually necessary. However, if any of our names had contained a comma, we would need to put that name in quotes to prevent Mapnik from incorrectly interpreting the comma as the separator between data values. When Mapnik sees a value in quotes, it knows not to split it, and it discards the quotes.
- You might notice that some of the coordinates are outside the area we are going to map. This is not a problem: Mapnik will work out what gets displayed and what doesn't.
Save this program in a file called 020-points.py
and run it by typing:
python 020-points.py
You should see no error messages, and you should see a new file in your working directory called 020-points.png. This is a new map image, and should be a light-coloured rectangle 480 pixels wide by 320 pixels high, with some small black squares marking the positions of the items in the data file, as shown above.