diff --git a/bindings/python/mapnik/__init__.py b/bindings/python/mapnik/__init__.py index 17ccb0686..c308fbfd4 100644 --- a/bindings/python/mapnik/__init__.py +++ b/bindings/python/mapnik/__init__.py @@ -650,6 +650,7 @@ __all__ = [ 'Feature', 'Featureset', 'FontEngine', + 'FontSet', 'Geometry2d', 'GlyphSymbolizer', 'Image', diff --git a/bindings/python/mapnik_envelope.cpp b/bindings/python/mapnik_envelope.cpp index 85f74bf9a..9cae79b2f 100644 --- a/bindings/python/mapnik_envelope.cpp +++ b/bindings/python/mapnik_envelope.cpp @@ -87,6 +87,14 @@ void (box2d::*re_center_p2)(coord const& ) = &box2d::r // clip void (box2d::*clip)(box2d const&) = &box2d::clip; +// deepcopy +box2d box2d_deepcopy(box2d & obj, boost::python::dict memo) +{ + // FIXME::ignore memo for now + box2d result(obj); + return result; +} + void export_envelope() { using namespace boost::python; @@ -270,5 +278,7 @@ void export_envelope() .def("__getitem__",&box2d::operator[]) .def("valid",&box2d::valid) .def_pickle(envelope_pickle_suite()) + .def("__deepcopy__", &box2d_deepcopy) ; + } diff --git a/bindings/python/mapnik_map.cpp b/bindings/python/mapnik_map.cpp index a51b26e9f..a947eeef8 100644 --- a/bindings/python/mapnik_map.cpp +++ b/bindings/python/mapnik_map.cpp @@ -31,7 +31,7 @@ #include #include #include - +#include #include "mapnik_enumeration.hpp" #include "python_optional.hpp" @@ -122,7 +122,8 @@ std::vector const& (Map::*layers_const)() const = &Map::layers; mapnik::parameters& (Map::*attr_nonconst)() = &Map::get_extra_attributes; mapnik::parameters& (Map::*params_nonconst)() = &Map::get_extra_parameters; -mapnik::feature_type_style find_style (mapnik::Map const& m, std::string const& name) + +mapnik::feature_type_style find_style(mapnik::Map const& m, std::string const& name) { boost::optional style = m.find_style(name); if (!style) @@ -133,6 +134,17 @@ mapnik::feature_type_style find_style (mapnik::Map const& m, std::string const& return *style; } +mapnik::font_set find_fontset(mapnik::Map const& m, std::string const& name) +{ + boost::optional fontset = m.find_fontset(name); + if (!fontset) + { + PyErr_SetString(PyExc_KeyError, "Invalid font_set name"); + boost::python::throw_error_already_set(); + } + return *fontset; +} + bool has_metawriter(mapnik::Map const& m) { if (m.metawriters().size() >=1) @@ -175,6 +187,15 @@ mapnik::featureset_ptr query_map_point(mapnik::Map const& m, int index, double x return m.query_map_point(idx, x, y); } +// deepcopy +mapnik::Map map_deepcopy(mapnik::Map & m, boost::python::dict memo) +{ + // FIXME: ignore memo for now + mapnik::Map result; + mapnik::util::deepcopy(m, result); + return result; +} + void export_map() { @@ -193,6 +214,7 @@ void export_map() ; python_optional (); + python_optional > (); class_ >("Layers") .def(vector_indexing_suite >()) ; @@ -229,6 +251,11 @@ void export_map() "False # you can only append styles with unique names\n" ) + .def("append_fontset",&Map::insert_fontset, + (arg("fontset")), + "Add a FontSet to the map." + ) + .def("buffered_envelope", &Map::get_buffered_extent, "Get the Box2d() of the Map given\n" @@ -261,9 +288,14 @@ void export_map() "...'maxy', 'minx', 'miny', 'width'\n" ) + .def("find_fontset",find_fontset, + (arg("name")), + "Find a fontset by name." + ) + .def("find_style", find_style, - (arg("style_name")), + (arg("name")), "Query the Map for a style by name and return\n" "a style object if found or raise KeyError\n" "style if not found.\n" @@ -452,6 +484,7 @@ void export_map() "about the hit areas rendered on the map.\n" ) + .def("__deepcopy__",&map_deepcopy) .add_property("extra_attributes",make_function(attr_nonconst,return_value_policy()),"TODO") .add_property("parameters",make_function(params_nonconst,return_value_policy()),"TODO") diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index 768bb6686..52b846a8d 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -47,6 +47,7 @@ void export_style(); void export_stroke(); void export_feature(); void export_featureset(); +void export_fontset(); void export_datasource(); void export_datasource_cache(); void export_symbolizer(); @@ -397,6 +398,7 @@ BOOST_PYTHON_MODULE(_mapnik) export_geometry(); export_feature(); export_featureset(); + export_fontset(); export_datasource(); export_parameters(); export_color(); diff --git a/include/mapnik/map.hpp b/include/mapnik/map.hpp index bbc5d9432..3e24b540b 100644 --- a/include/mapnik/map.hpp +++ b/include/mapnik/map.hpp @@ -218,7 +218,7 @@ public: * @param name The name of the fontset. * @return The fontset if found. If not found return the default map fontset. */ - font_set const& find_fontset(std::string const& name) const; + boost::optional find_fontset(std::string const& name) const; /*! \brief Get all fontsets * @return Const reference to fontsets diff --git a/include/mapnik/rule.hpp b/include/mapnik/rule.hpp index eb5a02c7e..d3185bef9 100644 --- a/include/mapnik/rule.hpp +++ b/include/mapnik/rule.hpp @@ -39,6 +39,7 @@ #include // boost +#include #include #include #include @@ -125,6 +126,8 @@ typedef boost::variant symbolizer; + + class rule { public: @@ -138,6 +141,104 @@ private: expression_ptr filter_; bool else_filter_; bool also_filter_; + + struct deepcopy_symbolizer : public boost::static_visitor<> + { + + void operator () (markers_symbolizer & sym) const + { + copy_path_ptr(sym); + } + + void operator () (point_symbolizer & sym) const + { + copy_path_ptr(sym); + } + + void operator () (polygon_pattern_symbolizer & sym) const + { + copy_path_ptr(sym); + } + + void operator () (line_pattern_symbolizer & sym) const + { + copy_path_ptr(sym); + } + + void operator () (raster_symbolizer & sym) const + { + raster_colorizer_ptr old_colorizer = sym.get_colorizer(); + raster_colorizer_ptr new_colorizer = raster_colorizer_ptr(); + new_colorizer->set_stops(old_colorizer->get_stops()); + new_colorizer->set_default_mode(old_colorizer->get_default_mode()); + new_colorizer->set_default_color(old_colorizer->get_default_color()); + new_colorizer->set_epsilon(old_colorizer->get_epsilon()); + sym.set_colorizer(new_colorizer); + } + + void operator () (text_symbolizer & sym) const + { + copy_text_ptr(sym); + } + + void operator () (shield_symbolizer & sym) const + { + copy_path_ptr(sym); + copy_text_ptr(sym); + } + + void operator () (building_symbolizer & sym) const + { + copy_height_ptr(sym); + } + + + template void operator () (T &sym) const + { + boost::ignore_unused_variable_warning(sym); + } + + private: + template + void copy_path_ptr(T & sym) const + { + std::string path = path_processor_type::to_string(*sym.get_filename()); + sym.set_filename( parse_path(path) ); + } + + template + void copy_text_ptr(T & sym) const + { + std::string name = to_expression_string(*sym.get_name()); + sym.set_name( parse_expression(name) ); + + // FIXME - orientation doesn't appear to be initialized in constructor? + //std::string orientation = to_expression_string(*sym->get_orientation()); + //sym->set_orientation( parse_expression(orientation) ); + + float text_size = sym.get_text_size(); + position displace = sym.get_displacement(); + vertical_alignment_e valign = sym.get_vertical_alignment(); + horizontal_alignment_e halign = sym.get_horizontal_alignment(); + justify_alignment_e jalign = sym.get_justify_alignment(); + + text_placements_ptr placements = text_placements_ptr(boost::make_shared()); + sym.set_placement_options( placements ); + sym.set_text_size(text_size); + sym.set_displacement(displace); + sym.set_vertical_alignment(valign); + sym.set_horizontal_alignment(halign); + sym.set_justify_alignment(jalign); + } + + template + void copy_height_ptr(T & sym) const + { + std::string height_expr = to_expression_string(sym.height()); + sym.set_height(parse_expression(height_expr,"utf8")); + } + }; + public: rule() : name_(), @@ -169,48 +270,17 @@ public: also_filter_(rhs.also_filter_) { if (deep_copy) { - //std::string expr = to_expression_string(rhs.filter_); - //filter_ = parse_expression(expr,"utf8"); - symbolizers::iterator it = syms_.begin(), - end = syms_.end(); + std::string expr = to_expression_string(*filter_); + filter_ = parse_expression(expr,"utf8"); + symbolizers::const_iterator it = syms_.begin(); + symbolizers::const_iterator end = syms_.end(); // FIXME - metawriter_ptr? - for(; it != end; ++it) { - - /*if (polygon_symbolizer *sym = boost::get(&(*it))) { - // no shared pointers - } else if (line_symbolizer *sym = boost::get(&(*it))) { - // no shared pointers - } else if (building_symbolizer *sym = boost::get(&(*it))) { - // no shared pointers - }*/ - - if (markers_symbolizer *sym = boost::get(&(*it))) { - copy_path_ptr(sym); - } else if (point_symbolizer *sym = boost::get(&(*it))) { - copy_path_ptr(sym); - } else if (polygon_pattern_symbolizer *sym = boost::get(&(*it))) { - copy_path_ptr(sym); - } else if (line_pattern_symbolizer *sym = boost::get(&(*it))) { - copy_path_ptr(sym); - } else if (raster_symbolizer *sym = boost::get(&(*it))) { - raster_colorizer_ptr old_colorizer = sym->get_colorizer(), - new_colorizer = raster_colorizer_ptr(); - - new_colorizer->set_stops( old_colorizer->get_stops()); - new_colorizer->set_default_mode( old_colorizer->get_default_mode() ); - new_colorizer->set_default_color( old_colorizer->get_default_color() ); - new_colorizer->set_epsilon( old_colorizer->get_epsilon() ); - - sym->set_colorizer(new_colorizer); - } else if (shield_symbolizer *sym = boost::get(&(*it))) { - copy_path_ptr(sym); - copy_text_ptr(sym); - } else if (text_symbolizer *sym = boost::get(&(*it))) { - copy_text_ptr(sym); - } + for(; it != end; ++it) + { + boost::apply_visitor(deepcopy_symbolizer(),*it); } } } @@ -340,40 +410,7 @@ private: filter_=rhs.filter_; else_filter_=rhs.else_filter_; also_filter_=rhs.also_filter_; - } - - template - void copy_path_ptr(T* sym) - { - std::string path = path_processor_type::to_string(*sym->get_filename()); - sym->set_filename( parse_path(path) ); - } - - template - void copy_text_ptr(T* sym) - { - std::string name = to_expression_string(*sym->get_name()); - sym->set_name( parse_expression(name) ); - - // FIXME - orientation doesn't appear to be initialized in constructor? - //std::string orientation = to_expression_string(*sym->get_orientation()); - //sym->set_orientation( parse_expression(orientation) ); - - float text_size = sym->get_text_size(); - position displace = sym->get_displacement(); - vertical_alignment_e valign = sym->get_vertical_alignment(); - horizontal_alignment_e halign = sym->get_horizontal_alignment(); - justify_alignment_e jalign = sym->get_justify_alignment(); - - text_placements_ptr placements = text_placements_ptr(boost::make_shared()); - sym->set_placement_options( placements ); - - sym->set_text_size(text_size); - sym->set_displacement(displace); - sym->set_vertical_alignment(valign); - sym->set_horizontal_alignment(halign); - sym->set_justify_alignment(jalign); - } + } }; } diff --git a/include/mapnik/util/deepcopy.hpp b/include/mapnik/util/deepcopy.hpp new file mode 100644 index 000000000..c81814518 --- /dev/null +++ b/include/mapnik/util/deepcopy.hpp @@ -0,0 +1,37 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +//$Id$ + +#ifndef MAPNIK_DEEPCOPY_HPP +#define MAPNIK_DEEPCOPY_HPP + +#include + +namespace mapnik { namespace util { + +// poor man deepcopy implementation +void deepcopy(Map const& map_in, Map & map_out); + +}} + +#endif // MAPNIK_DEEPSOPY_HPP diff --git a/src/build.py b/src/build.py index 927cda481..70abcbea3 100644 --- a/src/build.py +++ b/src/build.py @@ -106,6 +106,7 @@ source = Split( color.cpp box2d.cpp datasource_cache.cpp + deepcopy.cpp expression_string.cpp filter_factory.cpp feature_type_style.cpp diff --git a/src/deepcopy.cpp b/src/deepcopy.cpp new file mode 100644 index 000000000..0087e030d --- /dev/null +++ b/src/deepcopy.cpp @@ -0,0 +1,127 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +//$Id$ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// boost +#include +#include + + +namespace mapnik { namespace util { + +// poor man's deepcopy implementation + +void deepcopy(Map const& map_in, Map & map_out) +{ +// * width_(rhs.width_), +// * height_(rhs.height_), +// * srs_(rhs.srs_), +// * buffer_size_(rhs.buffer_size_), +// * background_(rhs.background_), +// * background_image_(rhs.background_image_), +// * styles_(rhs.styles_), +// metawriters_(rhs.metawriters_), +// fontsets_(rhs.fontsets_), +// * layers_(rhs.layers_), +// aspectFixMode_(rhs.aspectFixMode_), +// current_extent_(rhs.current_extent_), +// * maximum_extent_(rhs.maximum_extent_), +// * base_path_(rhs.base_path_), +// extra_attr_(rhs.extra_attr_), +// extra_params_(rhs.extra_params_) + + // width, height + map_out.resize(map_in.width(), map_in.height()); + // srs + map_out.set_srs(map_in.srs()); + // buffer_size + map_out.set_buffer_size(map_in.buffer_size()); + // background + boost::optional background = map_in.background(); + if (background) + { + map_out.set_background(*background); + } + // background_image + boost::optional background_image = map_in.background_image(); + if (background_image) + { + map_out.set_background_image(*background_image); + } + // maximum extent + boost::optional > max_extent = map_in.maximum_extent(); + if (max_extent) + { + map_out.set_maximum_extent(*max_extent); + } + // base_path + map_out.set_base_path(map_in.base_path()); + + // fontsets + typedef std::map fontsets; + BOOST_FOREACH ( fontsets::value_type const& kv,map_in.fontsets()) + { + map_out.insert_fontset(kv.first,kv.second); + } + + BOOST_FOREACH ( layer const& lyr_in, map_in.layers()) + { + layer lyr_out(lyr_in); + datasource_ptr ds_in = lyr_in.datasource(); + if (ds_in) + { + parameters p(ds_in->params()); + + // TODO : re-use datasource extent if already set. + datasource_ptr ds_out = datasource_cache::create(p); + if (ds_out) + { + lyr_out.set_datasource(ds_out); + } + } + map_out.addLayer(lyr_out); + } + typedef std::map style_cont; + typedef style_cont::value_type value_type; + + style_cont const& styles = map_in.styles(); + BOOST_FOREACH ( value_type const& kv, styles ) + { + feature_type_style const& style_in = kv.second; + feature_type_style style_out(style_in,true); // deep copy + map_out.insert_style(kv.first, style_out); + } + +} + +}} diff --git a/src/load_map.cpp b/src/load_map.cpp index 055948f3d..d7665000b 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -560,14 +560,19 @@ void map_parser::parse_font(font_set & fset, ptree const & f) { ensure_attrs(f, "Font", "face-name"); - std::string face_name = get_attr(f, "face-name", std::string()); - - if ( strict_ ) + optional face_name = get_opt_attr(f, "face-name"); + if (face_name) { - ensure_font_face( face_name ); + if ( strict_ ) + { + ensure_font_face(*face_name); + } + fset.add_face_name(*face_name); + } + else + { + throw config_error(std::string("Must have 'face-name' set")); } - - fset.add_face_name(face_name); } void map_parser::parse_layer( Map & map, ptree const & lay ) diff --git a/src/map.cpp b/src/map.cpp index 4b7af5513..e421998b8 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -204,14 +204,14 @@ bool Map::insert_fontset(std::string const& name, font_set const& fontset) { return fontsets_.insert(make_pair(name, fontset)).second; } - -font_set const& Map::find_fontset(std::string const& name) const + +boost::optional Map::find_fontset(std::string const& name) const { std::map::const_iterator itr = fontsets_.find(name); - if (itr!=fontsets_.end()) - return itr->second; - static font_set default_fontset; - return default_fontset; + if (itr != fontsets_.end()) + return boost::optional(itr->second); + else + return boost::optional() ; } std::map const& Map::fontsets() const diff --git a/tests/data/good_maps/fontset.xml b/tests/data/good_maps/fontset.xml new file mode 100644 index 000000000..4d004c002 --- /dev/null +++ b/tests/data/good_maps/fontset.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/python_tests/map_deepcopy_test.py b/tests/python_tests/map_deepcopy_test.py new file mode 100644 index 000000000..3b820979f --- /dev/null +++ b/tests/python_tests/map_deepcopy_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +from nose.tools import * +from utilities import execution_path +from copy import deepcopy + +import os, mapnik + +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 test_map_deepcopy1(): + m1 = mapnik.Map(256,256) + m1.append_style('style',mapnik.Style()) + m1.append_fontset('fontset',mapnik.FontSet()) + m2 = deepcopy(m1) + eq_(m2.width, m1.width) + eq_(m2.height, m2.height) + eq_(m2.srs, m1.srs) + eq_(m2.base, m1.base) + eq_(m2.background, m1.background) + eq_(m2.buffer_size, m1.buffer_size) + eq_(m2.aspect_fix_mode, m1.aspect_fix_mode) + eq_(m2.envelope(),m1.envelope()) + eq_(m2.buffered_envelope(),m1.buffered_envelope()) + eq_(m2.scale(),m1.scale()) + eq_(m2.scale_denominator(),m1.scale_denominator()) + eq_(m2.maximum_extent,m1.maximum_extent) + eq_(id(m2.has_metawriter()),id(m1.has_metawriter())) + eq_(id(m2.view_transform()),id(m1.view_transform())) + eq_(id(m2.extra_attributes),id(m1.extra_attributes)) + eq_(id(m2.parameters),id(m1.parameters)) + eq_(id(m2.layers),id(m1.layers)) + eq_(id(m2.layers),id(m1.layers)) + eq_(id(m2.find_style('style')),id(m2.find_style('style'))) + eq_(id(m2.find_fontset('fontset')),id(m2.find_fontset('fontset'))) + + +if __name__ == "__main__": + setup() + [eval(run)() for run in dir() if 'test_' in run] + diff --git a/tests/python_tests/object_test.py b/tests/python_tests/object_test.py index ef05d8281..9313bc507 100644 --- a/tests/python_tests/object_test.py +++ b/tests/python_tests/object_test.py @@ -71,8 +71,7 @@ def test_shieldsymbolizer_init(): eq_(s.transform, 'matrix(1, 0, 0, 1, 0, 0)') - raise Todo("FontSet pickling support needed: http://trac.mapnik.org/ticket/348") - eq_(s.fontset, '') + eq_(len(s.fontset.names), 0) # ShieldSymbolizer missing image file diff --git a/tests/python_tests/pickling_test.py b/tests/python_tests/pickling_test.py index a619dd372..56a1e1fb1 100644 --- a/tests/python_tests/pickling_test.py +++ b/tests/python_tests/pickling_test.py @@ -118,8 +118,7 @@ def test_textsymbolizer_pickle(): # r2300 eq_(s.minimum_padding, 0.0) - raise Todo("FontSet pickling support needed: http://trac.mapnik.org/ticket/348") - eq_(ts.fontset, ts2.fontset) + eq_(len(ts.fontset.names), 0) def test_map_pickle(): diff --git a/tests/python_tests/test_fontset.py b/tests/python_tests/test_fontset.py new file mode 100644 index 000000000..e41e64576 --- /dev/null +++ b/tests/python_tests/test_fontset.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +from nose.tools import * +from utilities import execution_path +from copy import deepcopy + +import os, mapnik + +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 test_loading_fontset_from_map(): + m = mapnik.Map(256,256) + mapnik.load_map(m,'../data/good_maps/fontset.xml') + fs = m.find_fontset('book-fonts') + eq_(len(fs.names),3) + eq_(list(fs.names),['DejaVu Sans Book','DejaVu Sans Oblique', 'does not exist']) + + +if __name__ == "__main__": + setup() + [eval(run)() for run in dir() if 'test_' in run] +