diff --git a/bindings/python/mapnik_label_collision_detector.cpp b/bindings/python/mapnik_label_collision_detector.cpp new file mode 100644 index 000000000..c81c6191a --- /dev/null +++ b/bindings/python/mapnik_label_collision_detector.cpp @@ -0,0 +1,123 @@ +/***************************************************************************** + * + * 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 + * + *****************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include + +using mapnik::label_collision_detector4; +using mapnik::box2d; +using mapnik::Map; +using boost::make_shared; + +namespace +{ + +boost::shared_ptr +create_label_collision_detector_from_extent(box2d const &extent) +{ + return make_shared(extent); +} + +boost::shared_ptr +create_label_collision_detector_from_map(Map const &m) +{ + double buffer = m.buffer_size(); + box2d extent(-buffer, -buffer, m.width() + buffer, m.height() + buffer); + return make_shared(extent); +} + +boost::python::list +make_label_boxes(boost::shared_ptr det) +{ + boost::python::list boxes; + + for (label_collision_detector4::query_iterator jtr = det->begin(); + jtr != det->end(); ++jtr) + { + boxes.append >(jtr->box); + } + + return boxes; +} + +} + +void export_label_collision_detector() +{ + using namespace boost::python; + + // for overload resolution + void (label_collision_detector4::*insert_box)(box2d const &) = &label_collision_detector4::insert; + + class_, boost::noncopyable> + ("LabelCollisionDetector", + "Object to detect collisions between labels, used in the rendering process.", + no_init) + + .def("__init__", make_constructor(create_label_collision_detector_from_extent), + "Creates an empty collision detection object with a given extent. Note " + "that the constructor from Map objects is a sensible default and usually " + "what you want to do.\n" + "\n" + "Example:\n" + ">>> m = Map(size_x, size_y)\n" + ">>> buf_sz = m.buffer_size\n" + ">>> extent = mapnik.Box2d(-buf_sz, -buf_sz, m.width + buf_sz, m.height + buf_sz)\n" + ">>> detector = mapnik.LabelCollisionDetector(extent)") + + .def("__init__", make_constructor(create_label_collision_detector_from_map), + "Creates an empty collision detection object matching the given Map object. " + "The created detector will have the same size, including the buffer, as the " + "map object. This is usually what you want to do.\n" + "\n" + "Example:\n" + ">>> m = Map(size_x, size_y)\n" + ">>> detector = mapnik.LabelCollisionDetector(m)") + + .def("extent", &label_collision_detector4::extent, return_value_policy(), + "Returns the total extent (bounding box) of all labels inside the detector.\n" + "\n" + "Example:\n" + ">>> detector.extent()\n" + "Box2d(573.252589209,494.789179821,584.261023823,496.83610261)") + + .def("boxes", &make_label_boxes, + "Returns a list of all the label boxes inside the detector.") + + .def("insert", insert_box, + "Insert a 2d box into the collision detector. This can be used to ensure that " + "some space is left clear on the map for later overdrawing, for example by " + "non-Mapnik processes.\n" + "\n" + "Example:\n" + ">>> m = Map(size_x, size_y)\n" + ">>> detector = mapnik.LabelCollisionDetector(m)" + ">>> detector.insert(mapnik.Box2d(196, 254, 291, 389))") + ; +} diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index 9d36aebc6..03c6fc75f 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -66,6 +66,7 @@ void export_view_transform(); void export_raster_colorizer(); void export_glyph_symbolizer(); void export_inmem_metawriter(); +void export_label_collision_detector(); #include #include @@ -120,6 +121,28 @@ void render(const mapnik::Map& map, Py_END_ALLOW_THREADS } +void render_with_detector( + const mapnik::Map &map, + mapnik::image_32 &image, + boost::shared_ptr detector, + double scale_factor = 1.0, + unsigned offset_x = 0u, + unsigned offset_y = 0u) +{ + Py_BEGIN_ALLOW_THREADS + try + { + mapnik::agg_renderer ren(map,image,detector); + ren.apply(); + } + catch (...) + { + Py_BLOCK_THREADS + throw; + } + Py_END_ALLOW_THREADS +} + void render_layer2(const mapnik::Map& map, mapnik::image_32& image, unsigned layer_idx) @@ -374,6 +397,7 @@ BOOST_PYTHON_FUNCTION_OVERLOADS(load_map_string_overloads, load_map_string, 2, 4 BOOST_PYTHON_FUNCTION_OVERLOADS(save_map_overloads, save_map, 2, 3) BOOST_PYTHON_FUNCTION_OVERLOADS(save_map_to_string_overloads, save_map_to_string, 1, 2) BOOST_PYTHON_FUNCTION_OVERLOADS(render_overloads, render, 2, 5) +BOOST_PYTHON_FUNCTION_OVERLOADS(render_with_detector_overloads, render_with_detector, 3, 6) BOOST_PYTHON_MODULE(_mapnik2) { @@ -427,6 +451,7 @@ BOOST_PYTHON_MODULE(_mapnik2) export_raster_colorizer(); export_glyph_symbolizer(); export_inmem_metawriter(); + export_label_collision_detector(); def("render_grid",&render_grid, ( arg("map"), @@ -503,6 +528,19 @@ BOOST_PYTHON_MODULE(_mapnik2) "\n" )); + def("render_with_detector", &render_with_detector, render_with_detector_overloads( + "\n" + "Render Map to an AGG image_32 using a pre-constructed detector.\n" + "\n" + "Usage:\n" + ">>> from mapnik import Map, Image, LabelCollisionDetector, render_with_detector, load_map\n" + ">>> m = Map(256,256)\n" + ">>> load_map(m,'mapfile.xml')\n" + ">>> im = Image(m.width,m.height)\n" + ">>> detector = LabelCollisionDetector(m)\n" + ">>> render_with_detector(m, im, detector)\n" + )); + def("render_layer", &render_layer2, (arg("map"),arg("image"),args("layer")) ); diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index 08a48d44a..3e5e218a5 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -40,6 +40,7 @@ // boost #include #include +#include // FIXME // forward declare so that @@ -61,7 +62,11 @@ class MAPNIK_DECL agg_renderer : public feature_style_processor { public: + // create with default, empty placement detector agg_renderer(Map const& m, T & pixmap, double scale_factor=1.0, unsigned offset_x=0, unsigned offset_y=0); + // create with external placement detector, possibly non-empty + agg_renderer(Map const &m, T & pixmap, boost::shared_ptr detector, + double scale_factor=1.0, unsigned offset_x=0, unsigned offset_y=0); ~agg_renderer(); void start_map_processing(Map const& map); void end_map_processing(Map const& map); @@ -122,8 +127,10 @@ private: CoordTransform t_; freetype_engine font_engine_; face_manager font_manager_; - label_collision_detector4 detector_; + boost::shared_ptr detector_; boost::scoped_ptr ras_ptr; + + void setup(Map const &m); }; } diff --git a/include/mapnik/label_collision_detector.hpp b/include/mapnik/label_collision_detector.hpp index f52d394e1..b9da67595 100644 --- a/include/mapnik/label_collision_detector.hpp +++ b/include/mapnik/label_collision_detector.hpp @@ -69,7 +69,7 @@ class label_collision_detector2 : boost::noncopyable typedef quad_tree > tree_t; tree_t tree_; public: - + explicit label_collision_detector2(box2d const& extent) : tree_(extent) {} @@ -138,6 +138,7 @@ public: //quad tree based label collission detector so labels dont appear within a given distance class label_collision_detector4 : boost::noncopyable { +public: struct label { label(box2d const& b) : box(b) {} @@ -146,11 +147,13 @@ class label_collision_detector4 : boost::noncopyable box2d box; UnicodeString text; }; - + +private: typedef quad_tree< label > tree_t; tree_t tree_; public: + typedef tree_t::query_iterator query_iterator; explicit label_collision_detector4(box2d const& extent) : tree_(extent) {} @@ -224,6 +227,9 @@ public: { return tree_.extent(); } + + query_iterator begin() { return tree_.query_in_box(extent()); } + query_iterator end() { return tree_.query_end(); } }; } diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index 42793cf49..ae482aa32 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -73,7 +73,7 @@ // boost #include - +#include // stl #ifdef MAPNIK_DEBUG @@ -121,8 +121,31 @@ agg_renderer::agg_renderer(Map const& m, T & pixmap, double scale_factor, uns t_(m.width(),m.height(),m.get_current_extent(),offset_x,offset_y), font_engine_(), font_manager_(font_engine_), - detector_(box2d(-m.buffer_size(), -m.buffer_size(), m.width() + m.buffer_size() ,m.height() + m.buffer_size())), + detector_(boost::make_shared(box2d(-m.buffer_size(), -m.buffer_size(), m.width() + m.buffer_size() ,m.height() + m.buffer_size()))), ras_ptr(new rasterizer) +{ + setup(m); +} + +template +agg_renderer::agg_renderer(Map const& m, T & pixmap, boost::shared_ptr detector, + double scale_factor, unsigned offset_x, unsigned offset_y) + : feature_style_processor(m, scale_factor), + pixmap_(pixmap), + width_(pixmap_.width()), + height_(pixmap_.height()), + scale_factor_(scale_factor), + t_(m.width(),m.height(),m.get_current_extent(),offset_x,offset_y), + font_engine_(), + font_manager_(font_engine_), + detector_(detector), + ras_ptr(new rasterizer) +{ + setup(m); +} + +template +void agg_renderer::setup(Map const &m) { boost::optional const& bg = m.background(); if (bg) pixmap_.set_background(*bg); @@ -189,7 +212,7 @@ void agg_renderer::start_layer_processing(layer const& lay) #endif if (lay.clear_label_cache()) { - detector_.clear(); + detector_->clear(); } } diff --git a/src/agg/process_glyph_symbolizer.cpp b/src/agg/process_glyph_symbolizer.cpp index e3b31b3cb..674c2eed7 100644 --- a/src/agg/process_glyph_symbolizer.cpp +++ b/src/agg/process_glyph_symbolizer.cpp @@ -77,12 +77,12 @@ void agg_renderer::process(glyph_symbolizer const& sym, // final box so we can check for a valid placement box2d dim = ren.prepare_glyphs(path.get()); box2d ext(x-dim.width()/2, y-dim.height()/2, x+dim.width()/2, y+dim.height()/2); - if ((sym.get_allow_overlap() || detector_.has_placement(ext)) && - (!sym.get_avoid_edges() || detector_.extent().contains(ext))) + if ((sym.get_allow_overlap() || detector_->has_placement(ext)) && + (!sym.get_avoid_edges() || detector_->extent().contains(ext))) { // Placement is valid, render glyph and update detector. ren.render(x, y); - detector_.insert(ext); + detector_->insert(ext); metawriter_with_properties writer = sym.get_metawriter(); if (writer.first) writer.first->add_box(ext, feature, t_, writer.second); } diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index d40b02ee7..cd9757a01 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -109,7 +109,7 @@ void agg_renderer::process(markers_symbolizer const& sym, } path_type path(t_,geom,prj_trans); - markers_placement placement(path, extent, detector_, + markers_placement placement(path, extent, *detector_, sym.get_spacing() * scale_factor_, sym.get_max_error(), sym.get_allow_overlap()); @@ -194,7 +194,7 @@ void agg_renderer::process(markers_symbolizer const& sym, box2d label_ext (px, py, px + dx +1, py + dy +1); if (sym.get_allow_overlap() || - detector_.has_placement(label_ext)) + detector_->has_placement(label_ext)) { agg::ellipse c(x, y, w, h); marker.concat_path(c); @@ -215,7 +215,7 @@ void agg_renderer::process(markers_symbolizer const& sym, ren.color(agg::rgba8(s_r, s_g, s_b, int(s_a*stroke_.get_opacity()))); agg::render_scanlines(*ras_ptr, sl_line, ren); } - detector_.insert(label_ext); + detector_->insert(label_ext); if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second); } } @@ -226,7 +226,7 @@ void agg_renderer::process(markers_symbolizer const& sym, marker.concat_path(arrow_); path_type path(t_,geom,prj_trans); - markers_placement placement(path, extent, detector_, + markers_placement placement(path, extent, *detector_, sym.get_spacing() * scale_factor_, sym.get_max_error(), sym.get_allow_overlap()); diff --git a/src/agg/process_point_symbolizer.cpp b/src/agg/process_point_symbolizer.cpp index fd57ec1d5..d9974d82e 100644 --- a/src/agg/process_point_symbolizer.cpp +++ b/src/agg/process_point_symbolizer.cpp @@ -99,13 +99,13 @@ void agg_renderer::process(point_symbolizer const& sym, label_ext.re_center(x,y); if (sym.get_allow_overlap() || - detector_.has_placement(label_ext)) + detector_->has_placement(label_ext)) { render_marker(floor(x - 0.5 * w),floor(y - 0.5 * h) ,**marker,tr, sym.get_opacity()); if (!sym.get_ignore_placement()) - detector_.insert(label_ext); + detector_->insert(label_ext); metawriter_with_properties writer = sym.get_metawriter(); if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second); } diff --git a/src/agg/process_shield_symbolizer.cpp b/src/agg/process_shield_symbolizer.cpp index 2008939f0..4a104d69c 100644 --- a/src/agg/process_shield_symbolizer.cpp +++ b/src/agg/process_shield_symbolizer.cpp @@ -135,7 +135,7 @@ void agg_renderer::process(shield_symbolizer const& sym, ren.set_halo_radius(sym.get_halo_radius() * scale_factor_); ren.set_opacity(sym.get_text_opacity()); - placement_finder finder(detector_); + placement_finder finder(*detector_); string_info info(text); @@ -210,13 +210,13 @@ void agg_renderer::process(shield_symbolizer const& sym, label_ext.re_center(label_x,label_y); } - if ( sym.get_allow_overlap() || detector_.has_placement(label_ext) ) + if ( sym.get_allow_overlap() || detector_->has_placement(label_ext) ) { render_marker(px,py,**marker,tr,sym.get_opacity()); box2d dim = ren.prepare_glyphs(&text_placement.placements[0]); ren.render(x,y); - detector_.insert(label_ext); + detector_->insert(label_ext); finder.update_detector(text_placement); if (writer.first) { writer.first->add_box(label_ext, feature, t_, writer.second); diff --git a/src/agg/process_text_symbolizer.cpp b/src/agg/process_text_symbolizer.cpp index 58b00a4d6..3d7440c19 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -110,7 +110,7 @@ void agg_renderer::process(text_symbolizer const& sym, ren.set_opacity(sym.get_text_opacity()); box2d dims(0,0,width_,height_); - placement_finder finder(detector_,dims); + placement_finder finder(*detector_,dims); string_info info(text);