User:Moresby/Understanding Mapnik/Using filters to represent data
So far we have plotted all features on a map in the same way: all points are drawn the same, or all roads are drawn the same. Maps often use different styles of drawing to represent different categories of features: points marking towns can be different colours or sizes depending on population, say, or roads can be different widths depending on their importance. The data files we have been using have some very basic categorisations of the towns and roads, and here we use those labels to affect the way they are represented on the map, using Mapnik filters.
#!/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 stroke object for railways.
stroke_rail = mapnik.Stroke()
stroke_rail.color = mapnik.Color('black')
stroke_rail.dasharray = [ (6, 2) ]
stroke_rail.width = 2
# Create point and line symbolizers.
point_symbolizer_city = mapnik.PointSymbolizer(mapnik.PathExpression('circle_red_16x16.png'))
point_symbolizer_town = mapnik.PointSymbolizer(mapnik.PathExpression('circle_red_8x8.png'))
line_symbolizer_rail = mapnik.LineSymbolizer(stroke_rail)
line_symbolizer_road = mapnik.LineSymbolizer()
line_symbolizer_mainroad = mapnik.LineSymbolizer(mapnik.Color('black'), 2)
line_symbolizer_motorway = mapnik.LineSymbolizer(mapnik.Color('lightblue'), 4)
# Create new rules and add the symbolizers.
r_city = mapnik.Rule()
r_city.symbols.append(point_symbolizer_city)
r_city.filter = mapnik.Filter('[type] = "city"')
r_town = mapnik.Rule()
r_town.symbols.append(point_symbolizer_town)
r_town.filter = mapnik.Filter('[type] = "town"')
r_rail = mapnik.Rule()
r_rail.symbols.append(line_symbolizer_rail)
r_rail.filter = mapnik.Filter('[type] = "rail"')
r_road = mapnik.Rule()
r_road.symbols.append(line_symbolizer_road)
r_road.filter = mapnik.Filter('[type] = "road"')
r_mainroad = mapnik.Rule()
r_mainroad.symbols.append(line_symbolizer_mainroad)
r_mainroad.filter = mapnik.Filter('[type] = "mainroad"')
r_motorway = mapnik.Rule()
r_motorway.symbols.append(line_symbolizer_motorway)
r_motorway.filter = mapnik.Filter('[type] = "motorway"')
# Create new styles and add the rules.
s_point = mapnik.Style()
s_point.rules.append(r_town)
s_point.rules.append(r_city)
s_line = mapnik.Style()
s_line.rules.append(r_rail)
s_line.rules.append(r_road)
s_line.rules.append(r_mainroad)
s_line.rules.append(r_motorway)
# Add the styles to the map.
m.append_style('point_style', s_point)
m.append_style('line_style', s_line)
# Specify our data sources.
ds_point = mapnik.CSV(file='data-places.csv')
ds_line = mapnik.CSV(file='data-roads.csv')
# Create new layers for the map, add the data sources and styles to
# those layers.
l_point = mapnik.Layer('point_layer')
l_point.datasource = ds_point
l_point.styles.append('point_style')
l_line = mapnik.Layer('line_layer')
l_line.datasource = ds_line
l_line.styles.append('line_style')
# Add the layers to the map. We want the points to appear in front of the
# lines, so we add the line layer first.
m.layers.append(l_line)
m.layers.append(l_point)
# 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, '050-filters.png', 'png')
- This program might look more complicated than previous examples, but there is just one main new concept being used; the rest is simply more of what we have already seen.
- We have seen before how Mapnik Rule objects are used to attach symbolizers to styles. So far we have been using the default settings of our Rule objects. But now we can start using Mapnik Filter objects. Filters allow us to specify which objects the symbolizer should render by providing an expression. When that expression is true, the object is passed to the symbolizer. In this example we are using only simple expressions such as
[type] = "rail"
but much more complex expressions are possible, and can be useful to fine-tune your map. - Overall, we have six different ways in which we want to represent objects: two for points (towns or cities) and four for lines (roads, main roads, motorways or railways). At lines 19 to 24 we create six separate symbolizers, one for each of these.
- At lines 27 to 49, we create a Rule object for each of our symbolizers, just as we have done before, but we also specify our filters, choosing the expressions to select just those items which we want to be represented by that symbolizer.
- The rest of the program is simply a case of joining up the objects as we have done before. The overall configuration can be seen in this diagram:
+-------+ | | +---------+ | |---- .background = -------| Color | | | +---------+ | | | | +------------+ +---------------------------+ | | | |---- .symbols.append() ----| PointSymbolizer | | | +---------+ | | | point_symbolizer_town | | | | |---- .rules.append() ----| Rule | +---------------------------+ | | | | | r_town | | | | | | | +----------------------+ | | | | | |---- .filter = ------------| Filter | | |---- .append_style() -----| Style | +------------+ | [type] = "town" | | | | | s_point | +----------------------+ | | /-------------\ | | | | | point_style | | | +------------+ +---------------------------+ | | \-------------/ | | | |---- .symbols.append() ----| PointSymbolizer | | | | | | | | point_symbolizer_city | | | | |---- .rules.append() ----| Rule | +---------------------------+ | | +---------+ | r_city | | | | | +----------------------+ | | | |---- .filter = ------------| Filter | | | +------------+ | [type] = "city" | | | +----------------------+ | | | | +------------+ +---------------------------+ | | +---------+ | |---- .symbols.append() ----| LineSymbolizer | | | | | | | | line_symbolizer_rail | | | | |---- .rules.append() ----| Rule | +---------------------------+ | | | | | r_rail | | | | | | | +----------------------+ | | | | | |---- .filter = ------------| Filter | | | | | +------------+ | [type] = "rail" | | | | | +----------------------+ | | | | | | | | +------------+ +---------------------------+ | | | | | |---- .symbols.append() ----| LineSymbolizer | | | | | | | | line_symbolizer_road | | | | |---- .rules.append() ----| Rule | +---------------------------+ | | | | | r_road | | Map | | | | | +----------------------+ | m | | | | |---- .filter = ------------| Filter | | |---- .append_style() -----| Style | +------------+ | [type] = "road" | | | | | s_line | +----------------------+ | | /-------------\ | | | | | line_style | | | +------------+ +---------------------------+ | | \-------------/ | | | |---- .symbols.append() ----| LineSymbolizer | | | | | | | | line_symbolizer_mainroad | | | | |---- .rules.append() ----| Rule | +---------------------------+ | | | | | r_mainroad | | | | | | | +----------------------+ | | | | | |---- .filter = ------------| Filter | | | | | +------------+ | [type] = "mainroad" | | | | | +----------------------+ | | | | | | | | +------------+ +---------------------------+ | | | | | |---- .symbols.append() ----| LineSymbolizer | | | | | | | | line_symbolizer_mainroad | | | | |---- .rules.append() ----| Rule | +---------------------------+ | | +---------+ | r_motorway | | | | | +----------------------+ | | | |---- .filter = ------------| Filter | | | +------------+ | [type] = "motorway" | | | +----------------------+ | | | | +---------+ +-----------------+ | | | |---- .datasource = ----| CSV | | | | Layer | | data-places.csv | | |---- .layers.append() ----| l_point | +-----------------+ | | | | | | | |---- .styles.append() | | +---------+ | | | /-------------\ | | | point_style | | | \-------------/ | | | | +---------+ +-----------------+ | | | |---- .datasource = ----| CSV | | | | Layer | | data-roads.csv | | |---- .layers.append() ----| l_line | +-----------------+ | | | | +-------+ | |---- .styles.append() +---------+ | /-------------\ | line_style | \-------------/
Save this program in a file called 050-filters.py
and run it by typing:
python 050-filters.py
You should see no error messages, and you should see a new file in your working directory called 050-filters.png. This is a new map image, and should be a light-coloured rectangle 480 pixels wide by 320 pixels high, with towns, cities and varieties of roads separately shown, as shown above.