python: add better handling of cairo/pycairo support, refactor rundemo.py, add basic tests for cairo surfaces (closes #392)

This commit is contained in:
Dane Springmeyer 2009-07-28 06:27:10 +00:00
parent 213384c7e3
commit 16f4efc07b
5 changed files with 175 additions and 87 deletions

View file

@ -73,6 +73,7 @@ void export_view_transform();
#if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO)
#include <pycairo.h>
static Pycairo_CAPI_t *Pycairo_CAPI;
#endif
void render(const mapnik::Map& map,mapnik::Image32& image, unsigned offset_x = 0, unsigned offset_y = 0)
@ -231,15 +232,35 @@ unsigned mapnik_svn_revision()
#endif
}
// indicator for cairo rendering support inside libmapnik
bool has_cairo()
{
#if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO)
#if defined(HAVE_CAIRO)
return true;
#else
return false;
#endif
}
// indicator for pycairo support in the python bindings
bool has_pycairo()
{
#if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO)
Pycairo_IMPORT;
/*!
Case where pycairo support has been compiled into
mapnik but at runtime the cairo python module
is unable to be imported and therefore Pycairo surfaces
and contexts cannot be passed to mapnik.render()
*/
if (Pycairo_CAPI == NULL) return false;
return true;
#else
return false;
#endif
}
BOOST_PYTHON_FUNCTION_OVERLOADS(load_map_overloads, load_map, 2, 3);
BOOST_PYTHON_FUNCTION_OVERLOADS(load_map_string_overloads, load_map_string, 2, 3);
BOOST_PYTHON_FUNCTION_OVERLOADS(save_map_overloads, save_map, 2, 3);
@ -448,6 +469,7 @@ BOOST_PYTHON_MODULE(_mapnik)
def("mapnik_version", &mapnik_version,"Get the Mapnik version number");
def("mapnik_svn_revision", &mapnik_svn_revision,"Get the Mapnik svn revision");
def("has_cairo", &has_cairo, "Get cairo library status");
def("has_pycairo", &has_pycairo, "Get pycairo module status");
using mapnik::symbolizer;
class_<symbolizer>("Symbolizer",no_init)

View file

@ -58,6 +58,8 @@ void register_cairo()
{
Pycairo_IMPORT;
if (Pycairo_CAPI == NULL) return;
boost::python::converter::registry::insert(&extract_surface, boost::python::type_id<PycairoSurface>());
boost::python::converter::registry::insert(&extract_context, boost::python::type_id<PycairoContext>());
}

View file

@ -24,7 +24,7 @@
# will want to be more selective.
try:
from mapnik import *
import mapnik
except:
print '\n\nThe mapnik library and python bindings must have been compiled and \
installed successfully before running this script.\n\n'
@ -33,11 +33,11 @@ installed successfully before running this script.\n\n'
# Instanciate a map, giving it a width and height. Remember: the word "map" is
# reserved in Python! :)
m = Map(800,600,"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs")
m = mapnik.Map(800,600,"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs")
# Set its background colour. More on colours later ...
m.background = Color('white')
m.background = mapnik.Color('white')
# Now we can start adding layers, in stacking order (i.e. bottom layer first)
@ -58,9 +58,9 @@ m.background = Color('white')
# password='mypassword'
# table= TODO
provpoly_lyr = Layer('Provinces')
provpoly_lyr = mapnik.Layer('Provinces')
provpoly_lyr.srs = "+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 +datum=NAD83 +units=m +no_defs"
provpoly_lyr.datasource = Shapefile(file='../data/boundaries', encoding='latin1')
provpoly_lyr.datasource = mapnik.Shapefile(file='../data/boundaries', encoding='latin1')
# We then define a style for the layer. A layer can have one or many styles.
# Styles are named, so they can be shared across different layers.
@ -70,18 +70,18 @@ provpoly_lyr.datasource = Shapefile(file='../data/boundaries', encoding='latin1'
# multiple styles in one layer is the same has having multiple layers.
# The paradigm is useful mostly as a convenience.
provpoly_style = Style()
provpoly_style = mapnik.Style()
# A Style needs one or more rules. A rule will normally consist of a filter
# for feature selection, and one or more symbolizers.
provpoly_rule_on = Rule()
provpoly_rule_on = mapnik.Rule()
# A Filter() allows the selection of features to which the symbology will
# be applied. More on Mapnik expressions can be found in Tutorial #2.
# A given feature can only match one filter per rule per style.
provpoly_rule_on.filter = Filter("[NAME_EN] = 'Ontario'")
provpoly_rule_on.filter = mapnik.Filter("[NAME_EN] = 'Ontario'")
# Here a symbolizer is defined. Available are:
# - LineSymbolizer(Color(),<width>)
@ -95,12 +95,12 @@ provpoly_rule_on.filter = Filter("[NAME_EN] = 'Ontario'")
# - Color(<string>) where <string> will be something like '#00FF00'
# or '#0f0' or 'green'
provpoly_rule_on.symbols.append(PolygonSymbolizer(Color(250, 190, 183)))
provpoly_rule_on.symbols.append(mapnik.PolygonSymbolizer(mapnik.Color(250, 190, 183)))
provpoly_style.rules.append(provpoly_rule_on)
provpoly_rule_qc = Rule()
provpoly_rule_qc.filter = Filter("[NOM_FR] = 'Québec'")
provpoly_rule_qc.symbols.append(PolygonSymbolizer(Color(217, 235, 203)))
provpoly_rule_qc = mapnik.Rule()
provpoly_rule_qc.filter = mapnik.Filter("[NOM_FR] = 'Québec'")
provpoly_rule_qc.symbols.append(mapnik.PolygonSymbolizer(mapnik.Color(217, 235, 203)))
provpoly_style.rules.append(provpoly_rule_qc)
# Add the style to the map, giving it a name. This is the name that will be
@ -123,14 +123,14 @@ m.layers.append(provpoly_lyr)
# A simple example ...
qcdrain_lyr = Layer('Quebec Hydrography')
qcdrain_lyr = mapnik.Layer('Quebec Hydrography')
qcdrain_lyr.srs = "+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 +datum=NAD83 +units=m +no_defs"
qcdrain_lyr.datasource = Shapefile(file='../data/qcdrainage')
qcdrain_lyr.datasource = mapnik.Shapefile(file='../data/qcdrainage')
qcdrain_style = Style()
qcdrain_rule = Rule()
qcdrain_rule.filter = Filter('[HYC] = 8')
qcdrain_rule.symbols.append(PolygonSymbolizer(Color(153, 204, 255)))
qcdrain_style = mapnik.Style()
qcdrain_rule = mapnik.Rule()
qcdrain_rule.filter = mapnik.Filter('[HYC] = 8')
qcdrain_rule.symbols.append(mapnik.PolygonSymbolizer(mapnik.Color(153, 204, 255)))
qcdrain_style.rules.append(qcdrain_rule)
m.append_style('drainage', qcdrain_style)
@ -141,31 +141,31 @@ m.layers.append(qcdrain_lyr)
# attributes, and same desired style), so we're going to
# re-use the style defined in the above layer for the next one.
ondrain_lyr = Layer('Ontario Hydrography')
ondrain_lyr = mapnik.Layer('Ontario Hydrography')
ondrain_lyr.srs = "+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 +datum=NAD83 +units=m +no_defs"
ondrain_lyr.datasource = Shapefile(file='../data/ontdrainage')
ondrain_lyr.datasource = mapnik.Shapefile(file='../data/ontdrainage')
ondrain_lyr.styles.append('drainage')
m.layers.append(ondrain_lyr)
# Provincial boundaries
provlines_lyr = Layer('Provincial borders')
provlines_lyr = mapnik.Layer('Provincial borders')
provlines_lyr.srs = "+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 +datum=NAD83 +units=m +no_defs"
provlines_lyr.datasource = Shapefile(file='../data/boundaries_l')
provlines_lyr.datasource = mapnik.Shapefile(file='../data/boundaries_l')
# Here we define a "dash dot dot dash" pattern for the provincial boundaries.
provlines_stk = Stroke()
provlines_stk = mapnik.Stroke()
provlines_stk.add_dash(8, 4)
provlines_stk.add_dash(2, 2)
provlines_stk.add_dash(2, 2)
provlines_stk.color = Color('black')
provlines_stk.color = mapnik.Color('black')
provlines_stk.width = 1.0
provlines_style = Style()
provlines_rule = Rule()
provlines_rule.symbols.append(LineSymbolizer(provlines_stk))
provlines_style = mapnik.Style()
provlines_rule = mapnik.Rule()
provlines_rule.symbols.append(mapnik.LineSymbolizer(provlines_stk))
provlines_style.rules.append(provlines_rule)
m.append_style('provlines', provlines_style)
@ -174,22 +174,22 @@ m.layers.append(provlines_lyr)
# Roads 3 and 4 (The "grey" roads)
roads34_lyr = Layer('Roads')
roads34_lyr = mapnik.Layer('Roads')
roads34_lyr.srs = "+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 +datum=NAD83 +units=m +no_defs"
# create roads datasource (we're going to re-use it later)
roads34_lyr.datasource = Shapefile(file='../data/roads')
roads34_lyr.datasource = mapnik.Shapefile(file='../data/roads')
roads34_style = Style()
roads34_rule = Rule()
roads34_rule.filter = Filter('[CLASS] = 3 or [CLASS] = 4')
roads34_style = mapnik.Style()
roads34_rule = mapnik.Rule()
roads34_rule.filter = mapnik.Filter('[CLASS] = 3 or [CLASS] = 4')
# With lines of a certain width, you can control how the ends
# are closed off using line_cap as below.
roads34_rule_stk = Stroke()
roads34_rule_stk.color = Color(171,158,137)
roads34_rule_stk.line_cap = line_cap.ROUND_CAP
roads34_rule_stk = mapnik.Stroke()
roads34_rule_stk.color = mapnik.Color(171,158,137)
roads34_rule_stk.line_cap = mapnik.line_cap.ROUND_CAP
# Available options are:
# line_cap: BUTT_CAP, SQUARE_CAP, ROUND_CAP
@ -199,7 +199,7 @@ roads34_rule_stk.line_cap = line_cap.ROUND_CAP
# can be set to a numerical value.
roads34_rule_stk.width = 2.0
roads34_rule.symbols.append(LineSymbolizer(roads34_rule_stk))
roads34_rule.symbols.append(mapnik.LineSymbolizer(roads34_rule_stk))
roads34_style.rules.append(roads34_rule)
m.append_style('smallroads', roads34_style)
@ -208,31 +208,31 @@ m.layers.append(roads34_lyr)
# Roads 2 (The thin yellow ones)
roads2_lyr = Layer('Roads')
roads2_lyr = mapnik.Layer('Roads')
roads2_lyr.srs = "+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 +datum=NAD83 +units=m +no_defs"
# Just get a copy from roads34_lyr
roads2_lyr.datasource = roads34_lyr.datasource
roads2_style_1 = Style()
roads2_rule_1 = Rule()
roads2_rule_1.filter = Filter('[CLASS] = 2')
roads2_rule_stk_1 = Stroke()
roads2_rule_stk_1.color = Color(171,158,137)
roads2_rule_stk_1.line_cap = line_cap.ROUND_CAP
roads2_style_1 = mapnik.Style()
roads2_rule_1 = mapnik.Rule()
roads2_rule_1.filter = mapnik.Filter('[CLASS] = 2')
roads2_rule_stk_1 = mapnik.Stroke()
roads2_rule_stk_1.color = mapnik.Color(171,158,137)
roads2_rule_stk_1.line_cap = mapnik.line_cap.ROUND_CAP
roads2_rule_stk_1.width = 4.0
roads2_rule_1.symbols.append(LineSymbolizer(roads2_rule_stk_1))
roads2_rule_1.symbols.append(mapnik.LineSymbolizer(roads2_rule_stk_1))
roads2_style_1.rules.append(roads2_rule_1)
m.append_style('road-border', roads2_style_1)
roads2_style_2 = Style()
roads2_rule_2 = Rule()
roads2_rule_2.filter = Filter('[CLASS] = 2')
roads2_rule_stk_2 = Stroke()
roads2_rule_stk_2.color = Color(255,250,115)
roads2_rule_stk_2.line_cap = line_cap.ROUND_CAP
roads2_style_2 = mapnik.Style()
roads2_rule_2 = mapnik.Rule()
roads2_rule_2.filter = mapnik.Filter('[CLASS] = 2')
roads2_rule_stk_2 = mapnik.Stroke()
roads2_rule_stk_2.color = mapnik.Color(255,250,115)
roads2_rule_stk_2.line_cap = mapnik.line_cap.ROUND_CAP
roads2_rule_stk_2.width = 2.0
roads2_rule_2.symbols.append(LineSymbolizer(roads2_rule_stk_2))
roads2_rule_2.symbols.append(mapnik.LineSymbolizer(roads2_rule_stk_2))
roads2_style_2.rules.append(roads2_rule_2)
m.append_style('road-fill', roads2_style_2)
@ -244,29 +244,29 @@ m.layers.append(roads2_lyr)
# Roads 1 (The big orange ones, the highways)
roads1_lyr = Layer('Roads')
roads1_lyr = mapnik.Layer('Roads')
roads1_lyr.srs = "+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 +datum=NAD83 +units=m +no_defs"
roads1_lyr.datasource = roads34_lyr.datasource
roads1_style_1 = Style()
roads1_rule_1 = Rule()
roads1_rule_1.filter = Filter('[CLASS] = 1')
roads1_rule_stk_1 = Stroke()
roads1_rule_stk_1.color = Color(188,149,28)
roads1_rule_stk_1.line_cap = line_cap.ROUND_CAP
roads1_style_1 = mapnik.Style()
roads1_rule_1 = mapnik.Rule()
roads1_rule_1.filter = mapnik.Filter('[CLASS] = 1')
roads1_rule_stk_1 = mapnik.Stroke()
roads1_rule_stk_1.color = mapnik.Color(188,149,28)
roads1_rule_stk_1.line_cap = mapnik.line_cap.ROUND_CAP
roads1_rule_stk_1.width = 7.0
roads1_rule_1.symbols.append(LineSymbolizer(roads1_rule_stk_1))
roads1_rule_1.symbols.append(mapnik.LineSymbolizer(roads1_rule_stk_1))
roads1_style_1.rules.append(roads1_rule_1)
m.append_style('highway-border', roads1_style_1)
roads1_style_2 = Style()
roads1_rule_2 = Rule()
roads1_rule_2.filter = Filter('[CLASS] = 1')
roads1_rule_stk_2 = Stroke()
roads1_rule_stk_2.color = Color(242,191,36)
roads1_rule_stk_2.line_cap = line_cap.ROUND_CAP
roads1_style_2 = mapnik.Style()
roads1_rule_2 = mapnik.Rule()
roads1_rule_2.filter = mapnik.Filter('[CLASS] = 1')
roads1_rule_stk_2 = mapnik.Stroke()
roads1_rule_stk_2.color = mapnik.Color(242,191,36)
roads1_rule_stk_2.line_cap = mapnik.line_cap.ROUND_CAP
roads1_rule_stk_2.width = 5.0
roads1_rule_2.symbols.append(LineSymbolizer(roads1_rule_stk_2))
roads1_rule_2.symbols.append(mapnik.LineSymbolizer(roads1_rule_stk_2))
roads1_style_2.rules.append(roads1_rule_2)
m.append_style('highway-fill', roads1_style_2)
@ -278,25 +278,25 @@ m.layers.append(roads1_lyr)
# Populated Places
popplaces_lyr = Layer('Populated Places')
popplaces_lyr = mapnik.Layer('Populated Places')
popplaces_lyr.srs = "+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 +datum=NAD83 +units=m +no_defs"
popplaces_lyr.datasource = Shapefile(file='../data/popplaces',encoding='latin1')
popplaces_lyr.datasource = mapnik.Shapefile(file='../data/popplaces',encoding='latin1')
popplaces_style = Style()
popplaces_rule = Rule()
popplaces_style = mapnik.Style()
popplaces_rule = mapnik.Rule()
# And here we have a TextSymbolizer, used for labeling.
# The first parameter is the name of the attribute to use as the source of the
# text to label with. Then there is font size in points (I think?), and colour.
popplaces_text_symbolizer = TextSymbolizer('GEONAME',
popplaces_text_symbolizer = mapnik.TextSymbolizer('GEONAME',
'DejaVu Sans Book',
10, Color('black'))
10, mapnik.Color('black'))
# We set a "halo" around the text, which looks like an outline if thin enough,
# or an outright background if large enough.
popplaces_text_symbolizer.set_label_placement=label_placement.POINT_PLACEMENT
popplaces_text_symbolizer.halo_fill = Color('white')
popplaces_text_symbolizer.set_label_placement= mapnik.label_placement.POINT_PLACEMENT
popplaces_text_symbolizer.halo_fill = mapnik.Color('white')
popplaces_text_symbolizer.halo_radius = 1
popplaces_text_symbolizer.avoid_edges = True
popplaces_rule.symbols.append(popplaces_text_symbolizer)
@ -310,11 +310,11 @@ m.layers.append(popplaces_lyr)
# Draw map
# Set the initial extent of the map in 'master' spherical Mercator projection
m.zoom_to_box(Envelope(-8024477.28459,5445190.38849,-7381388.20071,5662941.44855))
m.zoom_to_box(mapnik.Envelope(-8024477.28459,5445190.38849,-7381388.20071,5662941.44855))
# Render two maps, two PNGs, one JPEG.
im = Image(m.width,m.height)
render(m, im)
im = mapnik.Image(m.width,m.height)
mapnik.render(m, im)
# Save image to files
images = []
@ -326,20 +326,29 @@ im.save('demo.jpg', 'jpeg')
images.append('demo.jpg')
# Render cairo examples
try:
if mapnik.has_pycairo():
import cairo
surface = cairo.SVGSurface('demo.svg', m.width,m.height)
render(m, surface)
svg_surface = cairo.SVGSurface('demo.svg', m.width,m.height)
mapnik.render(m, svg_surface)
svg_surface.finish()
images.append('demo.svg')
surface = cairo.PDFSurface('demo.pdf', m.width,m.height)
render(m, surface)
pdf_surface = cairo.PDFSurface('demo.pdf', m.width,m.height)
mapnik.render(m, pdf_surface)
images.append('demo.pdf')
except:
print '\n\nSkipping cairo examples as Pycairo not available'
pdf_surface.finish()
postscript_surface = cairo.PSSurface('demo.ps', m.width,m.height)
mapnik.render(m, postscript_surface)
images.append('demo.ps')
postscript_surface.finish()
else:
print '\n\nSkipping cairo examples as Mapnik Pycairo support not available'
print "\n\n", len(images), "maps have been rendered in the current directory:"
for image in images:
print "-", image
print "\n\nHave a look!\n\n"
save_map(m,"map.xml")
mapnik.save_map(m,"map.xml")

View file

@ -0,0 +1,55 @@
#!/usr/bin/env python
import os
import mapnik
from nose.tools import *
from utilities import execution_path,Todo
def setup():
# All of the paths used are relative, if we run the tests
# from another directory we need to chdir()
os.chdir(execution_path('.'))
def _pycairo_surface(type,sym):
if mapnik.has_pycairo():
import cairo
test_cairo_file = 'test.%s' % type
m = mapnik.Map(256,256)
mapnik.load_map(m,'../data/good_maps/%s_symbolizer.xml' % sym)
surface = getattr(cairo,'%sSurface' % type.upper())(test_cairo_file, m.width,m.height)
mapnik.render(m, surface)
surface.finish()
if os.path.exists(test_cairo_file):
os.remove(test_cairo_file)
return True
else:
# Fail, the file wasn't written
return False
def test_pycairo_svg_surface():
return _pycairo_surface('svg','point')
def test_pycairo_svg_surface():
return _pycairo_surface('svg','building')
def test_pycairo_svg_surface():
return _pycairo_surface('svg','polygon')
def test_pycairo_svg_surface():
return _pycairo_surface('pdf','point')
def test_pycairo_svg_surface():
return _pycairo_surface('pdf','building')
def test_pycairo_svg_surface():
return _pycairo_surface('pdf','polygon')
def test_pycairo_svg_surface():
return _pycairo_surface('ps','point')
def test_pycairo_svg_surface():
return _pycairo_surface('ps','building')
def test_pycairo_svg_surface():
return _pycairo_surface('ps','polygon')