diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index a926a385e..c1544a5bb 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -73,6 +73,7 @@ void export_view_transform(); #if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) #include +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,7 +469,8 @@ 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",no_init) ; diff --git a/bindings/python/python_cairo.cpp b/bindings/python/python_cairo.cpp index 5b3046217..30bcc164b 100644 --- a/bindings/python/python_cairo.cpp +++ b/bindings/python/python_cairo.cpp @@ -57,6 +57,8 @@ static void *extract_context(PyObject* op) void register_cairo() { Pycairo_IMPORT; + + if (Pycairo_CAPI == NULL) return; boost::python::converter::registry::insert(&extract_surface, boost::python::type_id()); boost::python::converter::registry::insert(&extract_context, boost::python::type_id()); diff --git a/demo/python/rundemo.py b/demo/python/rundemo.py index 32338f7d8..e2559371c 100644 --- a/demo/python/rundemo.py +++ b/demo/python/rundemo.py @@ -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(),) @@ -95,12 +95,12 @@ provpoly_rule_on.filter = Filter("[NAME_EN] = 'Ontario'") # - Color() where 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") diff --git a/tests/data/good_maps/point_symbolizers.xml b/tests/data/good_maps/point_symbolizer.xml similarity index 100% rename from tests/data/good_maps/point_symbolizers.xml rename to tests/data/good_maps/point_symbolizer.xml diff --git a/tests/python_tests/cairo_test.py b/tests/python_tests/cairo_test.py new file mode 100644 index 000000000..f0a2c2fcc --- /dev/null +++ b/tests/python_tests/cairo_test.py @@ -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')