Now that we have examined the Python interface to Mapnik, let's use this knowledge to take a closer look at the mapGenerator.py
module used in Chapter 7. As well as being a more comprehensive example of creating maps programmatically, the mapGenerator.py
module suggests ways in which you can write your own wrapper around Mapnik to simplify the creation of a map using Python code.
The mapGenerator.py
module defines just one function, generateMap()
, which allows you to create a simple map that is stored in a temporary file on disk. The method signature for the generateMap()
function looks like this:
def generateMap(datasource, minX, minY, maxX, maxY, mapWidth, mapHeight, hiliteExpr=None, background="#8080a0", hiliteLine="#000000", hiliteFill="#408000", normalLine="#404040", normalFill="#a0a0a0", points=None)
The parameters are as follows:
datasource
is a dictionary defining the datasource to use for this map. This dictionary should have at least one entry, type
, which defines the type of datasource. The following datasource types are supported: OGR, PostGIS, and SQLite. Any additional entries in this dictionary will be passed as keyword parameters to the datasource initializer.minX, minY, maxX
, and maxY
define the bounding box for the area to display, in map coordinates.mapWidth
and mapHeight
are the width and height of the image to generate, in pixels.hiliteExpr
is a Mapnik filter expression to use to identify the feature(s) to be highlighted.background
is the HTML color code to use for the background of the map.hiliteLine
and hiliteFill
are the HTML color codes to use for the line and fill for the highlighted features.normalLine
and normalFill
are the HTML color codes to use for the line and fill for the non-highlighted features.points
, if defined, should be a list of (long, lat, name) tuples identifying points to display on the map.Because many of these keyword parameters have default values, creating a simple map only requires the datasource, bounding box, and map dimensions to be specified. Everything else is optional.
The generateMap()
function creates a new map based on the given parameters, and stores the result as a PNG format image file in a temporary map cache directory. Upon completion, it returns the name and relative path to the newly-rendered image file.
So much for the public interface to the mapGenerator.py
module. Let's take a look inside to see how it works.
The module starts by creating a mapnik.Map
object to hold the generated map. We set the background color at the same time:
map = mapnik.Map(mapWidth, mapHeight, '+proj=longlat +datum=WGS84') map.background = mapnik.Color(background)
We next have to set up the Mapnik datasource to load our map data from. To simplify the job of accessing a datasource, the datasource parameter includes the type of datasource, as well as any additional entries which are passed as keyword parameters directly to the Mapnik datasource initializer:
srcType = datasource['type'] del datasource['type'] if srcType == "OGR": source = mapnik.Ogr(**datasource) elif srcType == "PostGIS": source = mapnik.PostGIS(**datasource) elif srcType == "SQLite": source = mapnik.SQLite(**datasource)
We then create our Layer object, and start defining the style that is used to draw the map data onto the map:
layer = mapnik.Layer("Layer") layer.datasource = source style = mapnik.Style()
We next set up a rule that only applies to the highlighted features:
rule = mapnik.Rule() if hiliteExpr != None: rule.filter = mapnik.Filter(hiliteExpr)
This rule will use the "highlight" line and fill colors:
rule.symbols.append(mapnik.PolygonSymbolizer( mapnik.Color(hiliteFill))) rule.symbols.append(mapnik.LineSymbolizer( mapnik.Stroke(mapnik.Color(hiliteLine), 0.1)))
We then add this rule to the style, and create another rule that only applies to the non-highlighted features:
style.rules.append(rule) rule = mapnik.Rule() rule.set_else(True)
This rule will use the "normal" line and fill colors:
rule.symbols.append(mapnik.PolygonSymbolizer( mapnik.Color(normalFill))) rule.symbols.append(mapnik.LineSymbolizer( mapnik.Stroke(mapnik.Color(normalLine), 0.1)))
We then add this rule to the style, and add the style to the map and layer:
style.rules.append(rule) map.append_style("Map Style", style) layer.styles.append("Map Style")
Finally, the layer is added to the map:
map.layers.append(layer)
One of the features of the generateMap()
function is that it can take a list of points and display them directly onto the map without having to store those points into a database. This is done through the use of a PointDataSource
datasource and a ShieldSymbolizer
to draw the points onto the map:
if points != None: pointDatasource = mapnik.PointDatasource() for long,lat,name in points: pointDatasource.add_point(long, lat, "name", name) layer = mapnik.Layer("Points") layer.datasource = pointDatasource style = mapnik.Style() rule = mapnik.Rule() pointImgFile = os.path.join(os.path.dirname(__file__), "point.png") shield = mapnik.ShieldSymbolizer( "name", "DejaVu Sans Bold", 10, mapnik.Color("#000000"), pointImgFile, "png", 9, 9) shield.displacement(0, 7) shield.unlock_image = True rule.symbols.append(shield) style.rules.append(rule) map.append_style("Point Style", style) layer.styles.append("Point Style") map.layers.append(layer)
Because the mapGenerator.py
module is designed to be used within a CGI script, the module makes use of a temporary map cache to hold the generated image files. Before it can render the map image, the generateMap()
function has to create the map cache if it doesn't already exist, and create a temporary file within the cache directory to hold the generated map:
scriptDir = os.path.dirname(__file__) cacheDir = os.path.join(scriptDir, "..", "mapCache") if not os.path.exists(cacheDir): os.mkdir(cacheDir) fd,filename = tempfile.mkstemp(".png", dir=cacheDir) os.close(fd)
Finally, we are ready to render the map into an image file, and return back to the caller the relative path to the generated map file:
map.zoom_to_box(mapnik.Envelope(minX, minY, maxX, maxY)) mapnik.render_to_file(map, filename, "png") return "../mapCache/" + os.path.basename(filename)
While in many ways the mapGenerator.py
module is quite simplistic and designed specifically to meet the needs of the DISTAL application presented in the previous chapter, it is worth examining this module in depth because it shows how the principle of encapsulation can be used to hide Mapnik's complexity and simplify the process of map generation. Using the generateMap()
function is infinitely easier than creating all the datasources, layers, symbolizers, rules, and styles each time a map has to be generated.
It would be a relatively easy task to design a more generic map generator that could handle a variety of datasources and map layers, as well as various ways of returning the results, without having to exhaustively define every object by hand. Designing and implementing such a module would be very worthwhile if you want to use Mapnik extensively from your Python programs. Hopefully, this section has given you some ideas about how you can proceed with implementing your own high-level Mapnik wrapper module.
18.191.165.62