Merge pull request #909 from MapQuest/expose-label-collision-detector

Exposed the label collision detector
This commit is contained in:
Artem Pavlenko 2011-10-21 12:20:21 -07:00
commit c7e1174d6b
10 changed files with 216 additions and 19 deletions

View file

@ -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 <boost/python.hpp>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/make_shared.hpp>
#include <mapnik/label_collision_detector.hpp>
#include <mapnik/map.hpp>
#include <list>
using mapnik::label_collision_detector4;
using mapnik::box2d;
using mapnik::Map;
using boost::make_shared;
namespace
{
boost::shared_ptr<label_collision_detector4>
create_label_collision_detector_from_extent(box2d<double> const &extent)
{
return make_shared<label_collision_detector4>(extent);
}
boost::shared_ptr<label_collision_detector4>
create_label_collision_detector_from_map(Map const &m)
{
double buffer = m.buffer_size();
box2d<double> extent(-buffer, -buffer, m.width() + buffer, m.height() + buffer);
return make_shared<label_collision_detector4>(extent);
}
boost::python::list
make_label_boxes(boost::shared_ptr<label_collision_detector4> det)
{
boost::python::list boxes;
for (label_collision_detector4::query_iterator jtr = det->begin();
jtr != det->end(); ++jtr)
{
boxes.append<box2d<double> >(jtr->box);
}
return boxes;
}
}
void export_label_collision_detector()
{
using namespace boost::python;
// for overload resolution
void (label_collision_detector4::*insert_box)(box2d<double> const &) = &label_collision_detector4::insert;
class_<label_collision_detector4, boost::shared_ptr<label_collision_detector4>, 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<copy_const_reference>(),
"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))")
;
}

View file

@ -66,6 +66,7 @@ void export_view_transform();
void export_raster_colorizer(); void export_raster_colorizer();
void export_glyph_symbolizer(); void export_glyph_symbolizer();
void export_inmem_metawriter(); void export_inmem_metawriter();
void export_label_collision_detector();
#include <mapnik/version.hpp> #include <mapnik/version.hpp>
#include <mapnik/value_error.hpp> #include <mapnik/value_error.hpp>
@ -120,6 +121,28 @@ void render(const mapnik::Map& map,
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
} }
void render_with_detector(
const mapnik::Map &map,
mapnik::image_32 &image,
boost::shared_ptr<mapnik::label_collision_detector4> detector,
double scale_factor = 1.0,
unsigned offset_x = 0u,
unsigned offset_y = 0u)
{
Py_BEGIN_ALLOW_THREADS
try
{
mapnik::agg_renderer<mapnik::image_32> ren(map,image,detector);
ren.apply();
}
catch (...)
{
Py_BLOCK_THREADS
throw;
}
Py_END_ALLOW_THREADS
}
void render_layer2(const mapnik::Map& map, void render_layer2(const mapnik::Map& map,
mapnik::image_32& image, mapnik::image_32& image,
unsigned layer_idx) 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_overloads, save_map, 2, 3)
BOOST_PYTHON_FUNCTION_OVERLOADS(save_map_to_string_overloads, save_map_to_string, 1, 2) 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_overloads, render, 2, 5)
BOOST_PYTHON_FUNCTION_OVERLOADS(render_with_detector_overloads, render_with_detector, 3, 6)
BOOST_PYTHON_MODULE(_mapnik2) BOOST_PYTHON_MODULE(_mapnik2)
{ {
@ -427,6 +451,7 @@ BOOST_PYTHON_MODULE(_mapnik2)
export_raster_colorizer(); export_raster_colorizer();
export_glyph_symbolizer(); export_glyph_symbolizer();
export_inmem_metawriter(); export_inmem_metawriter();
export_label_collision_detector();
def("render_grid",&render_grid, def("render_grid",&render_grid,
( arg("map"), ( arg("map"),
@ -503,6 +528,19 @@ BOOST_PYTHON_MODULE(_mapnik2)
"\n" "\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, def("render_layer", &render_layer2,
(arg("map"),arg("image"),args("layer")) (arg("map"),arg("image"),args("layer"))
); );

View file

@ -40,6 +40,7 @@
// boost // boost
#include <boost/utility.hpp> #include <boost/utility.hpp>
#include <boost/scoped_ptr.hpp> #include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
// FIXME // FIXME
// forward declare so that // forward declare so that
@ -61,7 +62,11 @@ class MAPNIK_DECL agg_renderer : public feature_style_processor<agg_renderer<T>
{ {
public: 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); 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<label_collision_detector4> detector,
double scale_factor=1.0, unsigned offset_x=0, unsigned offset_y=0);
~agg_renderer(); ~agg_renderer();
void start_map_processing(Map const& map); void start_map_processing(Map const& map);
void end_map_processing(Map const& map); void end_map_processing(Map const& map);
@ -122,8 +127,10 @@ private:
CoordTransform t_; CoordTransform t_;
freetype_engine font_engine_; freetype_engine font_engine_;
face_manager<freetype_engine> font_manager_; face_manager<freetype_engine> font_manager_;
label_collision_detector4 detector_; boost::shared_ptr<label_collision_detector4> detector_;
boost::scoped_ptr<rasterizer> ras_ptr; boost::scoped_ptr<rasterizer> ras_ptr;
void setup(Map const &m);
}; };
} }

View file

@ -138,6 +138,7 @@ public:
//quad tree based label collission detector so labels dont appear within a given distance //quad tree based label collission detector so labels dont appear within a given distance
class label_collision_detector4 : boost::noncopyable class label_collision_detector4 : boost::noncopyable
{ {
public:
struct label struct label
{ {
label(box2d<double> const& b) : box(b) {} label(box2d<double> const& b) : box(b) {}
@ -147,10 +148,12 @@ class label_collision_detector4 : boost::noncopyable
UnicodeString text; UnicodeString text;
}; };
private:
typedef quad_tree< label > tree_t; typedef quad_tree< label > tree_t;
tree_t tree_; tree_t tree_;
public: public:
typedef tree_t::query_iterator query_iterator;
explicit label_collision_detector4(box2d<double> const& extent) explicit label_collision_detector4(box2d<double> const& extent)
: tree_(extent) {} : tree_(extent) {}
@ -224,6 +227,9 @@ public:
{ {
return tree_.extent(); return tree_.extent();
} }
query_iterator begin() { return tree_.query_in_box(extent()); }
query_iterator end() { return tree_.query_end(); }
}; };
} }

View file

@ -73,7 +73,7 @@
// boost // boost
#include <boost/utility.hpp> #include <boost/utility.hpp>
#include <boost/make_shared.hpp>
// stl // stl
#ifdef MAPNIK_DEBUG #ifdef MAPNIK_DEBUG
@ -121,8 +121,31 @@ agg_renderer<T>::agg_renderer(Map const& m, T & pixmap, double scale_factor, uns
t_(m.width(),m.height(),m.get_current_extent(),offset_x,offset_y), t_(m.width(),m.height(),m.get_current_extent(),offset_x,offset_y),
font_engine_(), font_engine_(),
font_manager_(font_engine_), font_manager_(font_engine_),
detector_(box2d<double>(-m.buffer_size(), -m.buffer_size(), m.width() + m.buffer_size() ,m.height() + m.buffer_size())), detector_(boost::make_shared<label_collision_detector4>(box2d<double>(-m.buffer_size(), -m.buffer_size(), m.width() + m.buffer_size() ,m.height() + m.buffer_size()))),
ras_ptr(new rasterizer) ras_ptr(new rasterizer)
{
setup(m);
}
template <typename T>
agg_renderer<T>::agg_renderer(Map const& m, T & pixmap, boost::shared_ptr<label_collision_detector4> detector,
double scale_factor, unsigned offset_x, unsigned offset_y)
: feature_style_processor<agg_renderer>(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 <typename T>
void agg_renderer<T>::setup(Map const &m)
{ {
boost::optional<color> const& bg = m.background(); boost::optional<color> const& bg = m.background();
if (bg) pixmap_.set_background(*bg); if (bg) pixmap_.set_background(*bg);
@ -189,7 +212,7 @@ void agg_renderer<T>::start_layer_processing(layer const& lay)
#endif #endif
if (lay.clear_label_cache()) if (lay.clear_label_cache())
{ {
detector_.clear(); detector_->clear();
} }
} }

View file

@ -77,12 +77,12 @@ void agg_renderer<T>::process(glyph_symbolizer const& sym,
// final box so we can check for a valid placement // final box so we can check for a valid placement
box2d<double> dim = ren.prepare_glyphs(path.get()); box2d<double> dim = ren.prepare_glyphs(path.get());
box2d<double> ext(x-dim.width()/2, y-dim.height()/2, x+dim.width()/2, y+dim.height()/2); box2d<double> 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)) && if ((sym.get_allow_overlap() || detector_->has_placement(ext)) &&
(!sym.get_avoid_edges() || detector_.extent().contains(ext))) (!sym.get_avoid_edges() || detector_->extent().contains(ext)))
{ {
// Placement is valid, render glyph and update detector. // Placement is valid, render glyph and update detector.
ren.render(x, y); ren.render(x, y);
detector_.insert(ext); detector_->insert(ext);
metawriter_with_properties writer = sym.get_metawriter(); metawriter_with_properties writer = sym.get_metawriter();
if (writer.first) writer.first->add_box(ext, feature, t_, writer.second); if (writer.first) writer.first->add_box(ext, feature, t_, writer.second);
} }

View file

@ -109,7 +109,7 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
} }
path_type path(t_,geom,prj_trans); path_type path(t_,geom,prj_trans);
markers_placement<path_type, label_collision_detector4> placement(path, extent, detector_, markers_placement<path_type, label_collision_detector4> placement(path, extent, *detector_,
sym.get_spacing() * scale_factor_, sym.get_spacing() * scale_factor_,
sym.get_max_error(), sym.get_max_error(),
sym.get_allow_overlap()); sym.get_allow_overlap());
@ -194,7 +194,7 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
box2d<double> label_ext (px, py, px + dx +1, py + dy +1); box2d<double> label_ext (px, py, px + dx +1, py + dy +1);
if (sym.get_allow_overlap() || if (sym.get_allow_overlap() ||
detector_.has_placement(label_ext)) detector_->has_placement(label_ext))
{ {
agg::ellipse c(x, y, w, h); agg::ellipse c(x, y, w, h);
marker.concat_path(c); marker.concat_path(c);
@ -215,7 +215,7 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
ren.color(agg::rgba8(s_r, s_g, s_b, int(s_a*stroke_.get_opacity()))); ren.color(agg::rgba8(s_r, s_g, s_b, int(s_a*stroke_.get_opacity())));
agg::render_scanlines(*ras_ptr, sl_line, ren); 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); if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second);
} }
} }
@ -226,7 +226,7 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
marker.concat_path(arrow_); marker.concat_path(arrow_);
path_type path(t_,geom,prj_trans); path_type path(t_,geom,prj_trans);
markers_placement<path_type, label_collision_detector4> placement(path, extent, detector_, markers_placement<path_type, label_collision_detector4> placement(path, extent, *detector_,
sym.get_spacing() * scale_factor_, sym.get_spacing() * scale_factor_,
sym.get_max_error(), sym.get_max_error(),
sym.get_allow_overlap()); sym.get_allow_overlap());

View file

@ -99,13 +99,13 @@ void agg_renderer<T>::process(point_symbolizer const& sym,
label_ext.re_center(x,y); label_ext.re_center(x,y);
if (sym.get_allow_overlap() || 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()); render_marker(floor(x - 0.5 * w),floor(y - 0.5 * h) ,**marker,tr, sym.get_opacity());
if (!sym.get_ignore_placement()) if (!sym.get_ignore_placement())
detector_.insert(label_ext); detector_->insert(label_ext);
metawriter_with_properties writer = sym.get_metawriter(); metawriter_with_properties writer = sym.get_metawriter();
if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second); if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second);
} }

View file

@ -135,7 +135,7 @@ void agg_renderer<T>::process(shield_symbolizer const& sym,
ren.set_halo_radius(sym.get_halo_radius() * scale_factor_); ren.set_halo_radius(sym.get_halo_radius() * scale_factor_);
ren.set_opacity(sym.get_text_opacity()); ren.set_opacity(sym.get_text_opacity());
placement_finder<label_collision_detector4> finder(detector_); placement_finder<label_collision_detector4> finder(*detector_);
string_info info(text); string_info info(text);
@ -210,13 +210,13 @@ void agg_renderer<T>::process(shield_symbolizer const& sym,
label_ext.re_center(label_x,label_y); 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()); render_marker(px,py,**marker,tr,sym.get_opacity());
box2d<double> dim = ren.prepare_glyphs(&text_placement.placements[0]); box2d<double> dim = ren.prepare_glyphs(&text_placement.placements[0]);
ren.render(x,y); ren.render(x,y);
detector_.insert(label_ext); detector_->insert(label_ext);
finder.update_detector(text_placement); finder.update_detector(text_placement);
if (writer.first) { if (writer.first) {
writer.first->add_box(label_ext, feature, t_, writer.second); writer.first->add_box(label_ext, feature, t_, writer.second);

View file

@ -110,7 +110,7 @@ void agg_renderer<T>::process(text_symbolizer const& sym,
ren.set_opacity(sym.get_text_opacity()); ren.set_opacity(sym.get_text_opacity());
box2d<double> dims(0,0,width_,height_); box2d<double> dims(0,0,width_,height_);
placement_finder<label_collision_detector4> finder(detector_,dims); placement_finder<label_collision_detector4> finder(*detector_,dims);
string_info info(text); string_info info(text);