Find placements and render in process_group_symbolizer.
Create a group_symbolizer_helper for group placments, and extract some code from text_symbolizer_helper into a base class to share with group_symbolizer_helper. Also, move tolerance_iterator into its own header file. Use helper in process_group_symbolizer to find placement positions.
This commit is contained in:
parent
1ccdc5b76d
commit
76329028d2
9 changed files with 652 additions and 243 deletions
|
@ -24,8 +24,8 @@
|
|||
#define MAPNIK_GROUP_LAYOUT_MANAGER_HPP
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/pixel_position.hpp>
|
||||
#include <mapnik/box2d.hpp>
|
||||
#include <mapnik/pixel_position.hpp>
|
||||
#include <mapnik/group/group_layout.hpp>
|
||||
|
||||
// stl
|
||||
|
|
105
include/mapnik/group/group_symbolizer_helper.hpp
Normal file
105
include/mapnik/group/group_symbolizer_helper.hpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* Copyright (C) 2013 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
|
||||
*
|
||||
*****************************************************************************/
|
||||
#ifndef GROUP_SYMBOLIZER_HELPER_HPP
|
||||
#define GROUP_SYMBOLIZER_HELPER_HPP
|
||||
|
||||
//mapnik
|
||||
#include <mapnik/text/symbolizer_helpers.hpp>
|
||||
#include <mapnik/symbolizer.hpp>
|
||||
#include <mapnik/feature.hpp>
|
||||
#include <mapnik/marker.hpp>
|
||||
#include <mapnik/marker_cache.hpp>
|
||||
#include <mapnik/text/placement_finder.hpp>
|
||||
#include <mapnik/proj_transform.hpp>
|
||||
#include <mapnik/ctrans.hpp>
|
||||
|
||||
namespace mapnik {
|
||||
|
||||
class label_collision_detector4;
|
||||
typedef label_collision_detector4 DetectorType;
|
||||
|
||||
typedef std::list<pixel_position> pixel_position_list;
|
||||
|
||||
/** Helper object that does some of the GroupSymbolizer placement finding work. */
|
||||
class group_symbolizer_helper : public base_symbolizer_helper
|
||||
{
|
||||
public:
|
||||
struct box_element
|
||||
{
|
||||
box_element(box2d<double> const& box, value_unicode_string const& repeat_key = "")
|
||||
: box_(box),
|
||||
repeat_key_(repeat_key)
|
||||
{}
|
||||
box2d<double> box_;
|
||||
value_unicode_string repeat_key_;
|
||||
};
|
||||
|
||||
group_symbolizer_helper(group_symbolizer const& sym,
|
||||
feature_impl const& feature,
|
||||
proj_transform const& prj_trans,
|
||||
unsigned width,
|
||||
unsigned height,
|
||||
double scale_factor,
|
||||
CoordTransform const &t,
|
||||
DetectorType &detector,
|
||||
box2d<double> const& query_extent);
|
||||
|
||||
inline void add_box_element(box2d<double> const& box, value_unicode_string const& repeat_key = "")
|
||||
{
|
||||
box_elements_.push_back(box_element(box, repeat_key));
|
||||
}
|
||||
|
||||
inline void clear_box_elements()
|
||||
{
|
||||
box_elements_.clear();
|
||||
}
|
||||
|
||||
inline text_symbolizer_properties const& get_properties()
|
||||
{
|
||||
return placement_->properties;
|
||||
}
|
||||
|
||||
pixel_position_list const& get();
|
||||
|
||||
private:
|
||||
|
||||
/** Iterate over the given path, placing line-following labels or point labels with respect to label_spacing. */
|
||||
template <typename T>
|
||||
bool find_line_placements(T & path);
|
||||
/** Check if a point placement fits at given position */
|
||||
bool check_point_placement(pixel_position const& pos);
|
||||
/** Checks for collision. */
|
||||
bool collision(box2d<double> const& box, const value_unicode_string &repeat_key = "") const;
|
||||
double get_spacing(double path_length) const;
|
||||
|
||||
DetectorType &detector_;
|
||||
|
||||
/** Boxes and repeat keys to take into account when finding placement.
|
||||
* Boxes are relative to starting point of current placement.
|
||||
*/
|
||||
std::list<box_element> box_elements_;
|
||||
|
||||
pixel_position_list results_;
|
||||
};
|
||||
|
||||
} //namespace
|
||||
#endif // GROUP_SYMBOLIZER_HELPER_HPP
|
|
@ -33,10 +33,52 @@
|
|||
|
||||
namespace mapnik {
|
||||
|
||||
class base_symbolizer_helper
|
||||
{
|
||||
public:
|
||||
base_symbolizer_helper(symbolizer_base const& sym,
|
||||
feature_impl const& feature,
|
||||
proj_transform const& prj_trans,
|
||||
unsigned width,
|
||||
unsigned height,
|
||||
double scale_factor,
|
||||
CoordTransform const& t,
|
||||
box2d<double> const& query_extent);
|
||||
|
||||
protected:
|
||||
void initialize_geometries();
|
||||
void initialize_points();
|
||||
|
||||
//Input
|
||||
symbolizer_base const& sym_;
|
||||
feature_impl const& feature_;
|
||||
proj_transform const& prj_trans_;
|
||||
CoordTransform const& t_;
|
||||
box2d<double> dims_;
|
||||
box2d<double> const& query_extent_;
|
||||
float scale_factor_;
|
||||
bool clipped_;
|
||||
|
||||
//Processing
|
||||
/* Using list instead of vector, because we delete random elements and need iterators to stay valid. */
|
||||
/** Remaining geometries to be processed. */
|
||||
std::list<geometry_type*> geometries_to_process_;
|
||||
/** Remaining points to be processed. */
|
||||
std::list<pixel_position> points_;
|
||||
/** Geometry currently being processed. */
|
||||
std::list<geometry_type*>::iterator geo_itr_;
|
||||
/** Point currently being processed. */
|
||||
std::list<pixel_position>::iterator point_itr_;
|
||||
/** Use point placement. Otherwise line placement is used. */
|
||||
bool point_placement_;
|
||||
|
||||
text_placement_info_ptr placement_;
|
||||
};
|
||||
|
||||
/** Helper object that does all the TextSymbolizer placement finding
|
||||
* work except actually rendering the object. */
|
||||
|
||||
class text_symbolizer_helper
|
||||
class text_symbolizer_helper : public base_symbolizer_helper
|
||||
{
|
||||
public:
|
||||
template <typename FaceManagerT, typename DetectorT>
|
||||
|
@ -68,34 +110,12 @@ public:
|
|||
protected:
|
||||
bool next_point_placement();
|
||||
bool next_line_placement(bool clipped);
|
||||
void initialize_geometries();
|
||||
void initialize_points();
|
||||
|
||||
//Input
|
||||
text_symbolizer const& sym_;
|
||||
feature_impl const& feature_;
|
||||
proj_transform const& prj_trans_;
|
||||
CoordTransform const& t_;
|
||||
box2d<double> dims_;
|
||||
box2d<double> const& query_extent_;
|
||||
//Processing
|
||||
/* Using list instead of vector, because we delete random elements and need iterators to stay valid. */
|
||||
/** Remaining geometries to be processed. */
|
||||
std::list<geometry_type*> geometries_to_process_;
|
||||
/** Geometry currently being processed. */
|
||||
std::list<geometry_type*>::iterator geo_itr_;
|
||||
/** Remaining points to be processed. */
|
||||
std::list<pixel_position> points_;
|
||||
/** Point currently being processed. */
|
||||
std::list<pixel_position>::iterator point_itr_;
|
||||
/** Use point placement. Otherwise line placement is used. */
|
||||
bool point_placement_;
|
||||
placement_finder finder_;
|
||||
|
||||
/** Place text at points on a line instead of following the line (used for ShieldSymbolizer) .*/
|
||||
bool points_on_line_;
|
||||
|
||||
text_placement_info_ptr placement_;
|
||||
placement_finder finder_;
|
||||
|
||||
//ShieldSymbolizer only
|
||||
void init_marker();
|
||||
};
|
||||
|
|
100
include/mapnik/text/tolerance_iterator.hpp
Normal file
100
include/mapnik/text/tolerance_iterator.hpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* Copyright (C) 2013 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
|
||||
*
|
||||
*****************************************************************************/
|
||||
#ifndef MAPNIK_TOLERANCE_ITERATOR_HPP
|
||||
#define MAPNIK_TOLERANCE_ITERATOR_HPP
|
||||
|
||||
//mapnik
|
||||
#include <mapnik/debug.hpp>
|
||||
|
||||
namespace mapnik
|
||||
{
|
||||
|
||||
class tolerance_iterator
|
||||
{
|
||||
public:
|
||||
tolerance_iterator(double label_position_tolerance, double spacing)
|
||||
: tolerance_(label_position_tolerance > 0 ?
|
||||
label_position_tolerance : spacing/2.0),
|
||||
tolerance_delta_(std::max(1.0, tolerance_/100.0)),
|
||||
value_(0),
|
||||
initialized_(false),
|
||||
values_tried_(0)
|
||||
{
|
||||
}
|
||||
|
||||
~tolerance_iterator()
|
||||
{
|
||||
//std::cout << "values tried:" << values_tried_ << "\n";
|
||||
}
|
||||
|
||||
double get() const
|
||||
{
|
||||
return -value_;
|
||||
}
|
||||
|
||||
bool next()
|
||||
{
|
||||
++values_tried_;
|
||||
if (values_tried_ > 255)
|
||||
{
|
||||
/* This point should not be reached during normal operation. But I can think of
|
||||
* cases where very bad spacing and or tolerance values are choosen and the
|
||||
* placement finder tries an excessive number of placements.
|
||||
* 255 is an arbitrarily chosen limit.
|
||||
*/
|
||||
MAPNIK_LOG_WARN(placement_finder) << "Tried a huge number of placements. Please check "
|
||||
"'label-position-tolerance' and 'spacing' parameters "
|
||||
"of your TextSymbolizers.\n";
|
||||
return false;
|
||||
}
|
||||
if (!initialized_)
|
||||
{
|
||||
initialized_ = true;
|
||||
return true; //Always return value 0 as the first value.
|
||||
}
|
||||
if (value_ == 0)
|
||||
{
|
||||
value_ = tolerance_delta_;
|
||||
return true;
|
||||
}
|
||||
value_ = -value_;
|
||||
if (value_ > 0)
|
||||
{
|
||||
value_ += tolerance_delta_;
|
||||
}
|
||||
if (value_ > tolerance_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
double tolerance_;
|
||||
double tolerance_delta_;
|
||||
double value_;
|
||||
bool initialized_;
|
||||
unsigned values_tried_;
|
||||
};
|
||||
|
||||
}//ns mapnik
|
||||
|
||||
#endif // MAPNIK_TOLERANCE_ITERATOR_HPP
|
|
@ -27,10 +27,12 @@
|
|||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/feature_factory.hpp>
|
||||
#include <mapnik/attribute_collector.hpp>
|
||||
#include <mapnik/text/layout.hpp>
|
||||
#include <mapnik/group/group_layout_manager.hpp>
|
||||
|
||||
#include <mapnik/text/placement_finder.hpp>
|
||||
#include <mapnik/text/layout.hpp>
|
||||
#include <mapnik/text/renderer.hpp>
|
||||
#include <mapnik/group/group_layout_manager.hpp>
|
||||
#include <mapnik/group/group_symbolizer_helper.hpp>
|
||||
|
||||
#include <mapnik/geom_util.hpp>
|
||||
#include <mapnik/symbolizer.hpp>
|
||||
|
@ -194,7 +196,14 @@ private:
|
|||
{
|
||||
for (auto const &label : detector)
|
||||
{
|
||||
box_.expand_to_include(label.box);
|
||||
if (box_.width() > 0 && box_.height() > 0)
|
||||
{
|
||||
box_.expand_to_include(label.box);
|
||||
}
|
||||
else
|
||||
{
|
||||
box_ = label.box;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -214,7 +223,7 @@ struct thunk_renderer : public boost::static_visitor<>
|
|||
thunk_renderer(renderer_type &ren,
|
||||
buffer_type *buf,
|
||||
renderer_common &common,
|
||||
layout_offset const &offset)
|
||||
pixel_position const &offset)
|
||||
: ren_(ren), buf_(buf), common_(common), offset_(offset)
|
||||
{}
|
||||
|
||||
|
@ -245,7 +254,7 @@ struct thunk_renderer : public boost::static_visitor<>
|
|||
glyphs->set_marker(marker_info, new_marker_pos);
|
||||
}
|
||||
|
||||
ren.render(*glyphs);
|
||||
//ren.render(*glyphs); // <--- TODO: This causes seg fault?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,7 +268,7 @@ private:
|
|||
renderer_type &ren_;
|
||||
buffer_type *buf_;
|
||||
renderer_common &common_;
|
||||
layout_offset offset_;
|
||||
pixel_position offset_;
|
||||
};
|
||||
|
||||
geometry_type *origin_point(proj_transform const &prj_trans,
|
||||
|
@ -282,6 +291,8 @@ void agg_renderer<T0,T1>::process(group_symbolizer const& sym,
|
|||
mapnik::feature_impl & feature,
|
||||
proj_transform const& prj_trans)
|
||||
{
|
||||
box2d<double> clip_box = clipping_extent();
|
||||
|
||||
// find all column names referenced in the group rules and symbolizers
|
||||
std::set<std::string> columns;
|
||||
attribute_collector column_collector(columns);
|
||||
|
@ -384,8 +395,6 @@ void agg_renderer<T0,T1>::process(group_symbolizer const& sym,
|
|||
sub_feature->add_geometry(origin_point(prj_trans, common_));
|
||||
|
||||
// get the layout for this set of properties
|
||||
group_layout const &layout = props->get_layout();
|
||||
|
||||
for (auto const& rule : props->get_rules())
|
||||
{
|
||||
if (boost::apply_visitor(evaluate<Feature,value_type>(*sub_feature),
|
||||
|
@ -398,7 +407,7 @@ void agg_renderer<T0,T1>::process(group_symbolizer const& sym,
|
|||
bound_box bounds;
|
||||
render_thunk_list thunks;
|
||||
extract_bboxes extractor(bounds, thunks, *sub_feature, prj_trans,
|
||||
common_, text, clipping_extent());
|
||||
common_, text, clip_box);
|
||||
|
||||
for (auto const& sym : *rule)
|
||||
{
|
||||
|
@ -415,23 +424,56 @@ void agg_renderer<T0,T1>::process(group_symbolizer const& sym,
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: run placement_finder to get placements
|
||||
// create a symbolizer helper
|
||||
group_symbolizer_helper helper(sym, feature, prj_trans,
|
||||
common_.width_, common_.height_,
|
||||
common_.scale_factor_, common_.t_,
|
||||
*common_.detector_, clip_box);
|
||||
|
||||
// NOTE that the placement finder will have already inserted bboxes
|
||||
// for the found placements, so we don't need to do that again.
|
||||
// determine if we should be tracking repeat distance
|
||||
bool check_repeat = (helper.get_properties().minimum_distance > 0);
|
||||
|
||||
// TODO: foreach placement
|
||||
for (size_t i = 0; i < matches.size(); ++i)
|
||||
{
|
||||
if (check_repeat)
|
||||
{
|
||||
group_rule_ptr match_rule = matches[i].first;
|
||||
feature_ptr match_feature = matches[i].second;
|
||||
value_unicode_string rpt_key_value = "";
|
||||
|
||||
// get repeat key from matched group rule
|
||||
expression_ptr rpt_key_expr = match_rule->get_repeat_key();
|
||||
|
||||
// if no repeat key was defined, use default from group symbolizer
|
||||
if (!rpt_key_expr)
|
||||
{
|
||||
rpt_key_expr = get<expression_ptr>(sym, keys::repeat_key);
|
||||
}
|
||||
|
||||
// evalute the repeat key with the matched sub feature if we have one
|
||||
if (rpt_key_expr)
|
||||
{
|
||||
rpt_key_value = boost::apply_visitor(evaluate<Feature,value_type>(*match_feature), *rpt_key_expr).to_unicode();
|
||||
}
|
||||
helper.add_box_element(layout_manager.offset_box_at(i), rpt_key_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
helper.add_box_element(layout_manager.offset_box_at(i));
|
||||
}
|
||||
}
|
||||
|
||||
pixel_position_list positions = helper.get();
|
||||
for (pixel_position const& pos : positions)
|
||||
{
|
||||
pixel_position pos; // <-- pixel position given by placement_finder
|
||||
for (size_t layout_i = 0; layout_i < num_layout_thunks; ++layout_i)
|
||||
{
|
||||
layout_offset offset = layout_manager.offset_at(layout_i);
|
||||
const pixel_position &offset = layout_manager.offset_at(layout_i);
|
||||
pixel_position ren_pos = pos + offset;
|
||||
// to get actual placement, need to shift original bbox by layout
|
||||
// offset and placement finder position (assuming origin of
|
||||
// layout offsets is 0,0.
|
||||
offset += layout_offset(pos.x, pos.y);
|
||||
|
||||
thunk_renderer ren(*this, current_buffer_, common_, offset);
|
||||
thunk_renderer ren(*this, current_buffer_, common_, ren_pos);
|
||||
|
||||
for (render_thunk_ptr &thunk : layout_thunks[layout_i])
|
||||
{
|
||||
|
|
|
@ -235,6 +235,7 @@ source = Split(
|
|||
text/placements/simple.cpp
|
||||
group/group_layout_manager.cpp
|
||||
group/group_rule.cpp
|
||||
group/group_symbolizer_helper.cpp
|
||||
xml_tree.cpp
|
||||
config_error.cpp
|
||||
color_factory.cpp
|
||||
|
|
196
src/group/group_symbolizer_helper.cpp
Normal file
196
src/group/group_symbolizer_helper.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* Copyright (C) 2013 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
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/group/group_symbolizer_helper.hpp>
|
||||
#include <mapnik/label_collision_detector.hpp>
|
||||
#include <mapnik/geom_util.hpp>
|
||||
#include <mapnik/debug.hpp>
|
||||
#include <mapnik/symbolizer.hpp>
|
||||
#include <mapnik/value_types.hpp>
|
||||
#include <mapnik/text/placements/base.hpp>
|
||||
#include <mapnik/text/placements/dummy.hpp>
|
||||
#include <mapnik/text/vertex_cache.hpp>
|
||||
#include <mapnik/text/tolerance_iterator.hpp>
|
||||
|
||||
//agg
|
||||
#include "agg_conv_clip_polyline.h"
|
||||
|
||||
namespace mapnik {
|
||||
|
||||
group_symbolizer_helper::group_symbolizer_helper(
|
||||
const group_symbolizer &sym, const feature_impl &feature,
|
||||
const proj_transform &prj_trans,
|
||||
unsigned width, unsigned height, double scale_factor,
|
||||
const CoordTransform &t, DetectorType &detector,
|
||||
const box2d<double> &query_extent)
|
||||
: base_symbolizer_helper(sym, feature, prj_trans, width, height, scale_factor, t, query_extent),
|
||||
detector_(detector)
|
||||
{}
|
||||
|
||||
pixel_position_list const& group_symbolizer_helper::get()
|
||||
{
|
||||
results_.clear();
|
||||
|
||||
if (point_placement_)
|
||||
{
|
||||
for (pixel_position const& point : points_)
|
||||
{
|
||||
check_point_placement(point);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const& geom : geometries_to_process_)
|
||||
{
|
||||
if (clipped_)
|
||||
{
|
||||
typedef agg::conv_clip_polyline<geometry_type> clipped_geometry_type;
|
||||
typedef coord_transform<CoordTransform,clipped_geometry_type> path_type;
|
||||
|
||||
clipped_geometry_type clipped(*geom);
|
||||
clipped.clip_box(query_extent_.minx(), query_extent_.miny(),
|
||||
query_extent_.maxx(), query_extent_.maxy());
|
||||
path_type path(t_, clipped, prj_trans_);
|
||||
find_line_placements(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
typedef coord_transform<CoordTransform,geometry_type> path_type;
|
||||
path_type path(t_, *geom, prj_trans_);
|
||||
find_line_placements(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool group_symbolizer_helper::find_line_placements(T & path)
|
||||
{
|
||||
if (box_elements_.empty()) return true;
|
||||
|
||||
vertex_cache pp(path);
|
||||
|
||||
bool success = false;
|
||||
while (pp.next_subpath())
|
||||
{
|
||||
if (pp.length() <= 0.001)
|
||||
{
|
||||
success = check_point_placement(pp.current_position()) || success;
|
||||
continue;
|
||||
}
|
||||
|
||||
double spacing = get_spacing(pp.length());
|
||||
|
||||
pp.forward(spacing/2.0);
|
||||
do
|
||||
{
|
||||
tolerance_iterator tolerance_offset(placement_->properties.label_position_tolerance * scale_factor_, spacing); //TODO: Handle halign
|
||||
while (tolerance_offset.next())
|
||||
{
|
||||
vertex_cache::scoped_state state(pp);
|
||||
if (pp.move(tolerance_offset.get()) && check_point_placement(pp.current_position()))
|
||||
{
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (pp.forward(spacing));
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool group_symbolizer_helper::check_point_placement(pixel_position const& pos)
|
||||
{
|
||||
if (box_elements_.empty()) return false;
|
||||
|
||||
// offset boxes and check collision
|
||||
std::list< box2d<double> > real_boxes;
|
||||
for (auto const& box_elem : box_elements_)
|
||||
{
|
||||
box2d<double> real_box = box2d<double>(box_elem.box_);
|
||||
real_box.move(pos.x, pos.y);
|
||||
if (collision(real_box, box_elem.repeat_key_))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
real_boxes.push_back(real_box);
|
||||
}
|
||||
|
||||
// add boxes to collision detector
|
||||
std::list<box_element>::iterator elem_itr = box_elements_.begin();
|
||||
std::list< box2d<double> >::iterator real_itr = real_boxes.begin();
|
||||
while (elem_itr != box_elements_.end() && real_itr != real_boxes.end())
|
||||
{
|
||||
detector_.insert(*real_itr, elem_itr->repeat_key_);
|
||||
elem_itr++;
|
||||
real_itr++;
|
||||
}
|
||||
|
||||
results_.push_back(pos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool group_symbolizer_helper::collision(const box2d<double> &box, const value_unicode_string &repeat_key) const
|
||||
{
|
||||
if (!detector_.extent().intersects(box)
|
||||
||
|
||||
(placement_->properties.avoid_edges && !query_extent_.contains(box))
|
||||
||
|
||||
(placement_->properties.minimum_padding > 0 &&
|
||||
!query_extent_.contains(box + (scale_factor_ * placement_->properties.minimum_padding)))
|
||||
||
|
||||
(!placement_->properties.allow_overlap &&
|
||||
((repeat_key.length() == 0 && !detector_.has_point_placement(box, placement_->properties.minimum_distance * scale_factor_))
|
||||
||
|
||||
(repeat_key.length() > 0 && !detector_.has_placement(box, repeat_key, placement_->properties.minimum_distance * scale_factor_))))
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
double group_symbolizer_helper::get_spacing(double path_length) const
|
||||
{
|
||||
int num_labels = 1;
|
||||
if (placement_->properties.label_spacing > 0)
|
||||
{
|
||||
num_labels = static_cast<int>(floor(
|
||||
path_length / (placement_->properties.label_spacing * scale_factor_)));
|
||||
}
|
||||
|
||||
if (placement_->properties.force_odd_labels && num_labels % 2 == 0)
|
||||
{
|
||||
--num_labels;
|
||||
}
|
||||
if (num_labels <= 0)
|
||||
{
|
||||
num_labels = 1;
|
||||
}
|
||||
return path_length / num_labels;
|
||||
}
|
||||
|
||||
} //namespace
|
|
@ -29,6 +29,7 @@
|
|||
#include <mapnik/text/text_properties.hpp>
|
||||
#include <mapnik/text/placements_list.hpp>
|
||||
#include <mapnik/text/vertex_cache.hpp>
|
||||
#include <mapnik/text/tolerance_iterator.hpp>
|
||||
|
||||
// agg
|
||||
#include "agg_conv_clip_polyline.h"
|
||||
|
@ -39,74 +40,6 @@
|
|||
namespace mapnik
|
||||
{
|
||||
|
||||
class tolerance_iterator
|
||||
{
|
||||
public:
|
||||
tolerance_iterator(double label_position_tolerance, double spacing)
|
||||
: tolerance_(label_position_tolerance > 0 ?
|
||||
label_position_tolerance : spacing/2.0),
|
||||
tolerance_delta_(std::max(1.0, tolerance_/100.0)),
|
||||
value_(0),
|
||||
initialized_(false),
|
||||
values_tried_(0)
|
||||
{
|
||||
}
|
||||
|
||||
~tolerance_iterator()
|
||||
{
|
||||
//std::cout << "values tried:" << values_tried_ << "\n";
|
||||
}
|
||||
|
||||
double get() const
|
||||
{
|
||||
return -value_;
|
||||
}
|
||||
|
||||
bool next()
|
||||
{
|
||||
++values_tried_;
|
||||
if (values_tried_ > 255)
|
||||
{
|
||||
/* This point should not be reached during normal operation. But I can think of
|
||||
* cases where very bad spacing and or tolerance values are choosen and the
|
||||
* placement finder tries an excessive number of placements.
|
||||
* 255 is an arbitrarily chosen limit.
|
||||
*/
|
||||
MAPNIK_LOG_WARN(placement_finder) << "Tried a huge number of placements. Please check "
|
||||
"'label-position-tolerance' and 'spacing' parameters "
|
||||
"of your TextSymbolizers.\n";
|
||||
return false;
|
||||
}
|
||||
if (!initialized_)
|
||||
{
|
||||
initialized_ = true;
|
||||
return true; //Always return value 0 as the first value.
|
||||
}
|
||||
if (value_ == 0)
|
||||
{
|
||||
value_ = tolerance_delta_;
|
||||
return true;
|
||||
}
|
||||
value_ = -value_;
|
||||
if (value_ > 0)
|
||||
{
|
||||
value_ += tolerance_delta_;
|
||||
}
|
||||
if (value_ > tolerance_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
double tolerance_;
|
||||
double tolerance_delta_;
|
||||
double value_;
|
||||
bool initialized_;
|
||||
unsigned values_tried_;
|
||||
};
|
||||
|
||||
|
||||
// Output is centered around (0,0)
|
||||
static void rotated_box2d(box2d<double> & box, rotation const& rot, double width, double height)
|
||||
{
|
||||
|
|
|
@ -40,24 +40,150 @@
|
|||
|
||||
namespace mapnik {
|
||||
|
||||
template <typename FaceManagerT, typename DetectorT>
|
||||
text_symbolizer_helper::text_symbolizer_helper(const text_symbolizer &sym, const feature_impl &feature, const proj_transform &prj_trans, unsigned width, unsigned height, double scale_factor, const CoordTransform &t, FaceManagerT &font_manager, DetectorT &detector, const box2d<double> &query_extent)
|
||||
base_symbolizer_helper::base_symbolizer_helper(
|
||||
const symbolizer_base &sym, const feature_impl &feature,
|
||||
const proj_transform &prj_trans,
|
||||
unsigned width, unsigned height, double scale_factor,
|
||||
const CoordTransform &t, const box2d<double> &query_extent)
|
||||
: sym_(sym),
|
||||
feature_(feature),
|
||||
prj_trans_(prj_trans),
|
||||
t_(t),
|
||||
dims_(0, 0, width, height),
|
||||
query_extent_(query_extent),
|
||||
points_on_line_(false),
|
||||
placement_(mapnik::get<text_placements_ptr>(sym_, keys::text_placements_)->get_placement_info(scale_factor)),
|
||||
finder_(feature, detector, dims_, placement_, font_manager, scale_factor)
|
||||
scale_factor_(scale_factor),
|
||||
clipped_(mapnik::get<bool>(sym_, keys::clip, feature_, true /*TODO*/)),
|
||||
placement_(mapnik::get<text_placements_ptr>(sym_, keys::text_placements_)->get_placement_info(scale_factor))
|
||||
{
|
||||
initialize_geometries();
|
||||
if (!geometries_to_process_.size()) return;
|
||||
finder_.next_position();
|
||||
initialize_points();
|
||||
}
|
||||
|
||||
struct largest_bbox_first
|
||||
{
|
||||
bool operator() (geometry_type const* g0, geometry_type const* g1) const
|
||||
{
|
||||
box2d<double> b0 = g0->envelope();
|
||||
box2d<double> b1 = g1->envelope();
|
||||
return b0.width()*b0.height() > b1.width()*b1.height();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void base_symbolizer_helper::initialize_geometries()
|
||||
{
|
||||
// FIXME
|
||||
bool largest_box_only = false;//get<value_bool>(sym_, keys::largest_box_only);
|
||||
double minimum_path_length = 0; // get<value_double>(sym_, keys::minimum_path_length);
|
||||
for ( auto const& geom : feature_.paths())
|
||||
{
|
||||
// don't bother with empty geometries
|
||||
if (geom.size() == 0) continue;
|
||||
mapnik::geometry_type::types type = geom.type();
|
||||
if (type == geometry_type::types::Polygon)
|
||||
{
|
||||
if (minimum_path_length > 0)
|
||||
{
|
||||
box2d<double> gbox = t_.forward(geom.envelope(), prj_trans_);
|
||||
if (gbox.width() < minimum_path_length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO - calculate length here as well
|
||||
geometries_to_process_.push_back(const_cast<geometry_type*>(&geom));
|
||||
}
|
||||
|
||||
if (largest_box_only)
|
||||
{
|
||||
geometries_to_process_.sort(largest_bbox_first());
|
||||
geo_itr_ = geometries_to_process_.begin();
|
||||
geometries_to_process_.erase(++geo_itr_,geometries_to_process_.end());
|
||||
}
|
||||
geo_itr_ = geometries_to_process_.begin();
|
||||
}
|
||||
|
||||
void base_symbolizer_helper::initialize_points()
|
||||
{
|
||||
label_placement_enum how_placed = placement_->properties.label_placement;
|
||||
if (how_placed == LINE_PLACEMENT)
|
||||
{
|
||||
point_placement_ = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
point_placement_ = true;
|
||||
}
|
||||
|
||||
double label_x=0.0;
|
||||
double label_y=0.0;
|
||||
double z=0.0;
|
||||
|
||||
std::list<geometry_type*>::const_iterator itr = geometries_to_process_.begin();
|
||||
std::list<geometry_type*>::const_iterator end = geometries_to_process_.end();
|
||||
for (; itr != end; itr++)
|
||||
{
|
||||
geometry_type const& geom = **itr;
|
||||
if (how_placed == VERTEX_PLACEMENT)
|
||||
{
|
||||
geom.rewind(0);
|
||||
for(unsigned i = 0; i < geom.size(); i++)
|
||||
{
|
||||
geom.vertex(&label_x, &label_y);
|
||||
prj_trans_.backward(label_x, label_y, z);
|
||||
t_.forward(&label_x, &label_y);
|
||||
points_.push_back(pixel_position(label_x, label_y));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// https://github.com/mapnik/mapnik/issues/1423
|
||||
bool success = false;
|
||||
// https://github.com/mapnik/mapnik/issues/1350
|
||||
if (geom.type() == geometry_type::types::LineString)
|
||||
{
|
||||
success = label::middle_point(geom, label_x,label_y);
|
||||
}
|
||||
else if (how_placed == POINT_PLACEMENT)
|
||||
{
|
||||
success = label::centroid(geom, label_x, label_y);
|
||||
}
|
||||
else if (how_placed == INTERIOR_PLACEMENT)
|
||||
{
|
||||
success = label::interior_position(geom, label_x, label_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
MAPNIK_LOG_ERROR(symbolizer_helpers) << "ERROR: Unknown placement type in initialize_points()";
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
prj_trans_.backward(label_x, label_y, z);
|
||||
t_.forward(&label_x, &label_y);
|
||||
points_.push_back(pixel_position(label_x, label_y));
|
||||
}
|
||||
}
|
||||
}
|
||||
point_itr_ = points_.begin();
|
||||
}
|
||||
|
||||
template <typename FaceManagerT, typename DetectorT>
|
||||
text_symbolizer_helper::text_symbolizer_helper(
|
||||
const text_symbolizer &sym, const feature_impl &feature,
|
||||
const proj_transform &prj_trans,
|
||||
unsigned width, unsigned height, double scale_factor,
|
||||
const CoordTransform &t, FaceManagerT &font_manager,
|
||||
DetectorT &detector, const box2d<double> &query_extent)
|
||||
: base_symbolizer_helper(sym, feature, prj_trans, width, height, scale_factor, t, query_extent),
|
||||
finder_(feature, detector, dims_, placement_, font_manager, scale_factor),
|
||||
points_on_line_(false)
|
||||
{
|
||||
if (geometries_to_process_.size()) finder_.next_position();
|
||||
}
|
||||
|
||||
placements_list const& text_symbolizer_helper::get()
|
||||
{
|
||||
if (point_placement_)
|
||||
|
@ -66,7 +192,7 @@ placements_list const& text_symbolizer_helper::get()
|
|||
}
|
||||
else
|
||||
{
|
||||
while (next_line_placement(mapnik::get<bool>(sym_, keys::clip, feature_, true /*TODO*/)));
|
||||
while (next_line_placement(clipped_));
|
||||
}
|
||||
return finder_.placements();
|
||||
}
|
||||
|
@ -137,116 +263,6 @@ bool text_symbolizer_helper::next_point_placement()
|
|||
return false;
|
||||
}
|
||||
|
||||
struct largest_bbox_first
|
||||
{
|
||||
bool operator() (geometry_type const* g0, geometry_type const* g1) const
|
||||
{
|
||||
box2d<double> b0 = g0->envelope();
|
||||
box2d<double> b1 = g1->envelope();
|
||||
return b0.width()*b0.height() > b1.width()*b1.height();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void text_symbolizer_helper::initialize_geometries()
|
||||
{
|
||||
// FIXME
|
||||
bool largest_box_only = false;//get<value_bool>(sym_, keys::largest_box_only);
|
||||
double minimum_path_length = 0; // get<value_double>(sym_, keys::minimum_path_length);
|
||||
for ( auto const& geom : feature_.paths())
|
||||
{
|
||||
// don't bother with empty geometries
|
||||
if (geom.size() == 0) continue;
|
||||
mapnik::geometry_type::types type = geom.type();
|
||||
if (type == geometry_type::types::Polygon)
|
||||
{
|
||||
if (minimum_path_length > 0)
|
||||
{
|
||||
box2d<double> gbox = t_.forward(geom.envelope(), prj_trans_);
|
||||
if (gbox.width() < minimum_path_length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO - calculate length here as well
|
||||
geometries_to_process_.push_back(const_cast<geometry_type*>(&geom));
|
||||
}
|
||||
|
||||
if (largest_box_only)
|
||||
{
|
||||
geometries_to_process_.sort(largest_bbox_first());
|
||||
geo_itr_ = geometries_to_process_.begin();
|
||||
geometries_to_process_.erase(++geo_itr_,geometries_to_process_.end());
|
||||
}
|
||||
geo_itr_ = geometries_to_process_.begin();
|
||||
}
|
||||
|
||||
void text_symbolizer_helper::initialize_points()
|
||||
{
|
||||
label_placement_enum how_placed = placement_->properties.label_placement;
|
||||
if (how_placed == LINE_PLACEMENT)
|
||||
{
|
||||
point_placement_ = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
point_placement_ = true;
|
||||
}
|
||||
|
||||
double label_x=0.0;
|
||||
double label_y=0.0;
|
||||
double z=0.0;
|
||||
|
||||
std::list<geometry_type*>::const_iterator itr = geometries_to_process_.begin();
|
||||
std::list<geometry_type*>::const_iterator end = geometries_to_process_.end();
|
||||
for (; itr != end; itr++)
|
||||
{
|
||||
geometry_type const& geom = **itr;
|
||||
if (how_placed == VERTEX_PLACEMENT)
|
||||
{
|
||||
geom.rewind(0);
|
||||
for(unsigned i = 0; i < geom.size(); i++)
|
||||
{
|
||||
geom.vertex(&label_x, &label_y);
|
||||
prj_trans_.backward(label_x, label_y, z);
|
||||
t_.forward(&label_x, &label_y);
|
||||
points_.push_back(pixel_position(label_x, label_y));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// https://github.com/mapnik/mapnik/issues/1423
|
||||
bool success = false;
|
||||
// https://github.com/mapnik/mapnik/issues/1350
|
||||
if (geom.type() == geometry_type::types::LineString)
|
||||
{
|
||||
success = label::middle_point(geom, label_x,label_y);
|
||||
}
|
||||
else if (how_placed == POINT_PLACEMENT)
|
||||
{
|
||||
success = label::centroid(geom, label_x, label_y);
|
||||
}
|
||||
else if (how_placed == INTERIOR_PLACEMENT)
|
||||
{
|
||||
success = label::interior_position(geom, label_x, label_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
MAPNIK_LOG_ERROR(symbolizer_helpers) << "ERROR: Unknown placement type in initialize_points()";
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
prj_trans_.backward(label_x, label_y, z);
|
||||
t_.forward(&label_x, &label_y);
|
||||
points_.push_back(pixel_position(label_x, label_y));
|
||||
}
|
||||
}
|
||||
}
|
||||
point_itr_ = points_.begin();
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
template <typename FaceManagerT, typename DetectorT>
|
||||
|
@ -256,21 +272,15 @@ text_symbolizer_helper::text_symbolizer_helper(
|
|||
unsigned width, unsigned height, double scale_factor,
|
||||
const CoordTransform &t, FaceManagerT &font_manager,
|
||||
DetectorT &detector, const box2d<double> &query_extent)
|
||||
: sym_(sym),
|
||||
feature_(feature),
|
||||
prj_trans_(prj_trans),
|
||||
t_(t),
|
||||
dims_(0, 0, width, height),
|
||||
query_extent_(query_extent),
|
||||
points_on_line_(true),
|
||||
placement_(mapnik::get<text_placements_ptr>(sym_, keys::text_placements_)->get_placement_info(scale_factor)),
|
||||
finder_(feature, detector, dims_, placement_, font_manager, scale_factor)
|
||||
: base_symbolizer_helper(sym, feature, prj_trans, width, height, scale_factor, t, query_extent),
|
||||
finder_(feature, detector, dims_, placement_, font_manager, scale_factor),
|
||||
points_on_line_(true)
|
||||
{
|
||||
initialize_geometries();
|
||||
if (!geometries_to_process_.size()) return;
|
||||
finder_.next_position();
|
||||
initialize_points();
|
||||
init_marker();
|
||||
if (geometries_to_process_.size())
|
||||
{
|
||||
init_marker();
|
||||
finder_.next_position();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -316,6 +326,8 @@ void text_symbolizer_helper::init_marker()
|
|||
finder_.set_marker(std::make_shared<marker_info>(m, trans), bbox, unlock_image, marker_displacement);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
template text_symbolizer_helper::text_symbolizer_helper(const text_symbolizer &sym,
|
||||
const feature_impl &feature,
|
||||
const proj_transform &prj_trans,
|
||||
|
|
Loading…
Add table
Reference in a new issue