Extracted a lot of code from agg render process_group_symbolizer.

Move a lot of processing into a common process_group_symbolizer function.
Also, extract column collection out of process_group_symbolizer function.
This will reduce duplication needed for other renderers.
This commit is contained in:
Jordan Hollinger 2014-01-10 06:25:47 -05:00 committed by Jordan Hollinger
parent efc29649ab
commit 3d1c30db1e
6 changed files with 539 additions and 428 deletions

View file

@ -50,6 +50,8 @@
namespace mapnik {
class group_attribute_collector;
template <typename Container>
struct expression_attributes : boost::static_visitor<void>
{
@ -92,13 +94,28 @@ private:
Container& names_;
};
class group_attribute_collector : public mapnik::noncopyable
{
private:
std::set<std::string>& names_;
bool expand_index_columns_;
public:
group_attribute_collector(std::set<std::string>& names,
bool expand_index_columns)
: names_(names),
expand_index_columns_(expand_index_columns) {}
void operator() (group_symbolizer const& sym);
};
struct symbolizer_attributes : public boost::static_visitor<>
{
symbolizer_attributes(std::set<std::string>& names,
double & filter_factor)
: names_(names),
filter_factor_(filter_factor),
f_attr(names) {}
f_attr(names),
g_attr(names, true) {}
template <typename T>
void operator () (T const&) const {}
@ -226,12 +243,16 @@ struct symbolizer_attributes : public boost::static_visitor<>
}
}
void operator () (group_symbolizer const& sym);
void operator () (group_symbolizer const& sym)
{
g_attr(sym);
}
private:
std::set<std::string>& names_;
double & filter_factor_;
expression_attributes<std::set<std::string> > f_attr;
group_attribute_collector g_attr;
void collect_transform(transform_list_ptr const& trans_expr)
{
if (trans_expr)
@ -275,7 +296,8 @@ public:
}
};
inline void symbolizer_attributes::operator () (group_symbolizer const& sym)
inline void group_attribute_collector::operator() (group_symbolizer const& sym)
{
// find all column names referenced in the group symbolizer
std::set<std::string> group_columns;
@ -294,7 +316,10 @@ inline void symbolizer_attributes::operator () (group_symbolizer const& sym)
if (props) {
for (auto const& rule : props->get_rules())
{
// note that this recurses down on to the symbolizer
// internals too, so we get all free variables.
column_collector(*rule);
// still need to collect repeat key columns
if (rule->get_repeat_key())
{
boost::apply_visitor(rk_attr, *(rule->get_repeat_key()));
@ -305,14 +330,14 @@ inline void symbolizer_attributes::operator () (group_symbolizer const& sym)
// get indexed column names
for (auto const& col_name : group_columns)
{
if (col_name.find('%') != std::string::npos)
if (expand_index_columns_ && col_name.find('%') != std::string::npos)
{
// Note: ignore column name if it is '%' by itself.
// '%' is a special case to access the index value itself,
// rather than acessing indexed columns from data source.
if (col_name.size() > 1)
{
// indexed column name. add column name for each index value.
// Indexed column name. add column name for each index value.
int start = get<value_integer>(sym, keys::start_column);
int end = start + get<value_integer>(sym, keys::num_columns);
for (int col_idx = start; col_idx < end; ++col_idx)
@ -325,7 +350,8 @@ inline void symbolizer_attributes::operator () (group_symbolizer const& sym)
}
else
{
// non indexed column name. insert as is.
// This is not an indexed column, or we are ignoring indexes.
// Insert the name as is.
names_.insert(col_name);
}
}

View file

@ -0,0 +1,339 @@
/*****************************************************************************
*
* 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_RENDERER_COMMON_PROCESS_GROUP_SYMBOLIZER_HPP
#define MAPNIK_RENDERER_COMMON_PROCESS_GROUP_SYMBOLIZER_HPP
#include <mapnik/pixel_position.hpp>
#include <mapnik/marker_cache.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/feature_factory.hpp>
#include <mapnik/renderer_common.hpp>
#include <mapnik/label_collision_detector.hpp>
#include <mapnik/symbolizer.hpp>
#include <mapnik/attribute_collector.hpp>
#include <mapnik/group/group_layout.hpp>
#include <mapnik/group/group_layout_manager.hpp>
#include <mapnik/group/group_rule.hpp>
#include <mapnik/group/group_symbolizer_helper.hpp>
#include <mapnik/group/group_symbolizer_properties.hpp>
#include <mapnik/renderer_common/process_point_symbolizer.hpp>
#include <mapnik/text/placements_list.hpp>
#include <agg_trans_affine.h>
namespace mapnik {
class proj_transform;
/* General:
*
* The approach here is to run the normal symbolizers, but in
* a 'virtual' blank environment where the changes that they
* make are recorded (the detector, the render_* calls).
*
* The recorded boxes are then used to lay out the items and
* the offsets from old to new positions can be used to perform
* the actual rendering calls.
*
* This should allow us to re-use as much as possible of the
* existing symbolizer layout and rendering code while still
* being able to interpose our own decisions about whether
* a collision has occured or not.
*/
/**
* Thunk for rendering a particular instance of a point - this
* stores all the arguments necessary to re-render this point
* symbolizer at a later time.
*/
struct point_render_thunk
{
pixel_position pos_;
marker_ptr marker_;
agg::trans_affine tr_;
double opacity_;
composite_mode_e comp_op_;
point_render_thunk(pixel_position const &pos, marker const &m,
agg::trans_affine const &tr, double opacity,
composite_mode_e comp_op);
};
struct text_render_thunk
{
// need to keep these around, annoyingly, as the glyph_position
// struct keeps a pointer to the glyph_info, so we have to
// ensure the lifetime is the same.
placements_list placements_;
std::shared_ptr<std::vector<glyph_info> > glyphs_;
composite_mode_e comp_op_;
halo_rasterizer_enum halo_rasterizer_;
text_render_thunk(placements_list const &placements,
composite_mode_e comp_op,
halo_rasterizer_enum halo_rasterizer);
};
/**
* Variant type for render thunks to allow us to re-render them
* via a static visitor later.
*/
typedef boost::variant<point_render_thunk,
text_render_thunk> render_thunk;
typedef std::shared_ptr<render_thunk> render_thunk_ptr;
typedef std::list<render_thunk_ptr> render_thunk_list;
/**
* Base class for extracting the bounding boxes associated with placing
* a symbolizer at a fake, virtual point - not real geometry.
*
* The bounding boxes can be used for layout, and the thunks are
* used to re-render at locations according to the group layout.
*/
struct render_thunk_extractor : public boost::static_visitor<>
{
render_thunk_extractor(box2d<double> &box,
render_thunk_list &thunks,
mapnik::feature_impl &feature,
proj_transform const &prj_trans,
renderer_common &common,
box2d<double> const &clipping_extent);
void operator()(point_symbolizer const &sym) const;
void operator()(text_symbolizer const &sym) const;
template <typename T>
void operator()(T const &) const
{
// TODO: warning if unimplemented?
}
protected:
box2d<double> &box_;
render_thunk_list &thunks_;
mapnik::feature_impl &feature_;
proj_transform const &prj_trans_;
renderer_common &common_;
box2d<double> clipping_extent_;
void update_box() const;
};
geometry_type *origin_point(proj_transform const &prj_trans,
renderer_common const &common);
template <typename F>
void render_offset_placements(placements_list const& placements,
pixel_position const& offset,
F render_text) {
for (glyph_positions_ptr glyphs : placements)
{
// move the glyphs to the correct offset
pixel_position base_point = glyphs->get_base_point();
glyphs->set_base_point(base_point + offset);
// update the position of any marker
marker_info_ptr marker_info = glyphs->marker();
pixel_position marker_pos = glyphs->marker_pos();
if (marker_info)
{
glyphs->set_marker(marker_info, marker_pos + offset);
}
render_text(glyphs);
// Need to put the base_point back how it was in case something else calls this again
// (don't want to add offset twice) or calls with a different offset.
glyphs->set_base_point(base_point);
if (marker_info)
{
glyphs->set_marker(marker_info, marker_pos);
}
}
}
template <typename F>
void render_group_symbolizer(group_symbolizer const &sym,
mapnik::feature_impl &feature,
proj_transform const &prj_trans,
box2d<double> const &clipping_extent,
renderer_common &common,
F render_thunks)
{
// find all column names referenced in the group rules and symbolizers
std::set<std::string> columns;
group_attribute_collector column_collector(columns, false);
column_collector(sym);
group_symbolizer_properties_ptr props = get<group_symbolizer_properties_ptr>(sym, keys::group_properties);
// create a new context for the sub features of this group
context_ptr sub_feature_ctx = std::make_shared<mapnik::context_type>();
// populate new context with column names referenced in the group rules and symbolizers
for (auto const& col_name : columns)
{
sub_feature_ctx->push(col_name);
}
// keep track of the sub features that we'll want to symbolize
// along with the group rules that they matched
std::vector< std::pair<group_rule_ptr, feature_ptr> > matches;
// create a copied 'virtual' common renderer for processing sub feature symbolizers
// create an empty detector for it, so we are sure we won't hit anything
renderer_common virtual_renderer(common);
virtual_renderer.detector_ = std::make_shared<label_collision_detector4>(common.detector_->extent());
// keep track of which lists of render thunks correspond to
// entries in the group_layout_manager.
std::vector<render_thunk_list> layout_thunks;
size_t num_layout_thunks = 0;
// layout manager to store and arrange bboxes of matched features
group_layout_manager layout_manager(props->get_layout(), pixel_position(common.width_ / 2.0, common.height_ / 2.0));
// run feature or sub feature through the group rules & symbolizers
// for each index value in the range
int start = get<value_integer>(sym, keys::start_column);
int end = start + get<value_integer>(sym, keys::num_columns);
for (int col_idx = start; col_idx < end; ++col_idx)
{
// create sub feature with indexed column values
feature_ptr sub_feature = feature_factory::create(sub_feature_ctx, col_idx);
// copy the necessary columns to sub feature
for(auto const& col_name : columns)
{
if (col_name.find('%') != std::string::npos)
{
if (col_name.size() == 1)
{
// column name is '%' by itself, so give the index as the value
sub_feature->put(col_name, (value_integer)col_idx);
}
else
{
// indexed column
std::string col_idx_name = col_name;
boost::replace_all(col_idx_name, "%", boost::lexical_cast<std::string>(col_idx));
sub_feature->put(col_name, feature.get(col_idx_name));
}
}
else
{
// non-indexed column
sub_feature->put(col_name, feature.get(col_name));
}
}
// add a single point geometry at pixel origin
sub_feature->add_geometry(origin_point(prj_trans, common));
// get the layout for this set of properties
for (auto const& rule : props->get_rules())
{
if (boost::apply_visitor(evaluate<Feature,value_type>(*sub_feature),
*(rule->get_filter())).to_bool())
{
// add matched rule and feature to the list of things to draw
matches.push_back(std::make_pair(rule, sub_feature));
// construct a bounding box around all symbolizers for the matched rule
bound_box bounds;
render_thunk_list thunks;
render_thunk_extractor extractor(bounds, thunks, *sub_feature, prj_trans,
virtual_renderer, clipping_extent);
for (auto const& sym : *rule)
{
// TODO: construct layout and obtain bounding box
boost::apply_visitor(extractor, sym);
}
// add the bounding box to the layout manager
layout_manager.add_member_bound_box(bounds);
layout_thunks.emplace_back(std::move(thunks));
++num_layout_thunks;
break;
}
}
}
// create a symbolizer helper
group_symbolizer_helper helper(sym, feature, prj_trans,
common.width_, common.height_,
common.scale_factor_, common.t_,
*common.detector_, clipping_extent);
// determine if we should be tracking repeat distance
bool check_repeat = (helper.get_properties().minimum_distance > 0);
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)
{
for (size_t layout_i = 0; layout_i < num_layout_thunks; ++layout_i)
{
const pixel_position &offset = layout_manager.offset_at(layout_i);
pixel_position render_offset = pos + offset;
render_thunks(layout_thunks[layout_i], render_offset);
}
}
}
} // namespace mapnik
#endif /* MAPNIK_RENDERER_COMMON_PROCESS_GROUP_SYMBOLIZER_HPP */

View file

@ -23,6 +23,7 @@
#ifndef MAPNIK_RENDERER_COMMON_PROCESS_POINT_SYMBOLIZER_HPP
#define MAPNIK_RENDERER_COMMON_PROCESS_POINT_SYMBOLIZER_HPP
#include <mapnik/geom_util.hpp>
#include <mapnik/marker.hpp>
#include <mapnik/marker_cache.hpp>

View file

@ -25,224 +25,21 @@
#include <mapnik/agg_renderer.hpp>
#include <mapnik/agg_rasterizer.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/feature_factory.hpp>
#include <mapnik/attribute_collector.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>
#include <mapnik/parse_path.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/label_collision_detector.hpp>
#include <mapnik/renderer_common/process_point_symbolizer.hpp>
#include <mapnik/text/symbolizer_helpers.hpp>
#include <mapnik/renderer_common/process_group_symbolizer.hpp>
// agg
#include "agg_trans_affine.h"
// stl
#include <string>
// boost
#include <boost/variant/apply_visitor.hpp>
namespace mapnik {
/* General:
*
* The approach here is to run the normal symbolizers, but in
* a 'virtual' blank environment where the changes that they
* make are recorded (the detector, the render_* calls).
*
* The recorded boxes are then used to lay out the items and
* the offsets from old to new positions can be used to perform
* the actual rendering calls.
*
* This should allow us to re-use as much as possible of the
* existing symbolizer layout and rendering code while still
* being able to interpose our own decisions about whether
* a collision has occured or not.
*/
/**
* Thunk for rendering a particular instance of a point - this
* stores all the arguments necessary to re-render this point
* symbolizer at a later time.
*/
struct point_render_thunk
{
pixel_position pos_;
marker_ptr marker_;
agg::trans_affine tr_;
double opacity_;
composite_mode_e comp_op_;
point_render_thunk(pixel_position const &pos, marker const &m,
agg::trans_affine const &tr, double opacity,
composite_mode_e comp_op)
: pos_(pos), marker_(std::make_shared<marker>(m)),
tr_(tr), opacity_(opacity), comp_op_(comp_op)
{}
};
struct text_render_thunk
{
halo_rasterizer_enum halo_rasterizer_;
composite_mode_e comp_op_;
// need to keep these around, annoyingly, as the glyph_position
// struct keeps a pointer to the glyph_info, so we have to
// ensure the lifetime is the same.
placements_list placements_;
std::shared_ptr<std::vector<glyph_info> > glyphs_;
text_render_thunk(placements_list const &placements,
halo_rasterizer_enum halo_rasterizer,
composite_mode_e comp_op)
: halo_rasterizer_(halo_rasterizer), comp_op_(comp_op),
placements_(), glyphs_(std::make_shared<std::vector<glyph_info> >())
{
std::vector<glyph_info> &glyph_vec = *glyphs_;
size_t glyph_count = 0;
for (glyph_positions_ptr positions : placements)
{
glyph_count += std::distance(positions->begin(), positions->end());
}
glyph_vec.reserve(glyph_count);
for (glyph_positions_ptr positions : placements)
{
glyph_positions_ptr new_positions = std::make_shared<glyph_positions>();
new_positions->reserve(std::distance(positions->begin(), positions->end()));
glyph_positions &new_pos = *new_positions;
new_pos.set_base_point(positions->get_base_point());
if (positions->marker())
{
new_pos.set_marker(positions->marker(), positions->marker_pos());
}
for (glyph_position const &pos : *positions)
{
glyph_vec.push_back(*pos.glyph);
new_pos.push_back(glyph_vec.back(), pos.pos, pos.rot);
}
placements_.push_back(new_positions);
}
}
};
// Variant type for render thunks to allow us to re-render them
// via a static visitor later.
typedef boost::variant<point_render_thunk,
text_render_thunk> render_thunk;
typedef std::shared_ptr<render_thunk> render_thunk_ptr;
typedef std::list<render_thunk_ptr> render_thunk_list;
/**
* Visitor to extract the bounding boxes associated with placing
* a symbolizer at a fake, virtual point - not real geometry.
*
* The bounding boxes can be used for layout, and the thunks are
* used to re-render at locations according to the group layout.
*/
struct extract_bboxes : public boost::static_visitor<>
{
extract_bboxes(box2d<double> &box,
render_thunk_list &thunks,
mapnik::feature_impl &feature,
proj_transform const &prj_trans,
renderer_common const &common,
text_layout const &text,
box2d<double> const &clipping_extent)
: box_(box), thunks_(thunks), feature_(feature), prj_trans_(prj_trans),
common_(common), text_(text), clipping_extent_(clipping_extent)
{}
void operator()(point_symbolizer const &sym) const
{
// create an empty detector, so we are sure we won't hit
// anything
renderer_common common(common_);
common.detector_ = std::make_shared<label_collision_detector4>(common_.detector_->extent());
composite_mode_e comp_op = get<composite_mode_e>(sym, keys::comp_op, feature_, src_over);
render_point_symbolizer(
sym, feature_, prj_trans_, common,
[&](pixel_position const &pos, marker const &marker,
agg::trans_affine const &tr, double opacity) {
point_render_thunk thunk(pos, marker, tr, opacity, comp_op);
thunks_.push_back(std::make_shared<render_thunk>(std::move(thunk)));
});
update_box(*common.detector_);
}
void operator()(text_symbolizer const &sym) const
{
// create an empty detector, so we are sure we won't hit
// anything
renderer_common common(common_);
common.detector_ = std::make_shared<label_collision_detector4>(common_.detector_->extent());
box2d<double> clip_box = clipping_extent_;
text_symbolizer_helper helper(
sym, feature_, prj_trans_,
common.width_, common.height_,
common.scale_factor_,
common.t_, common.font_manager_, *common.detector_,
clip_box);
halo_rasterizer_enum halo_rasterizer = get<halo_rasterizer_enum>(sym, keys::halo_rasterizer, HALO_RASTERIZER_FULL);
composite_mode_e comp_op = get<composite_mode_e>(sym, keys::comp_op, feature_, src_over);
placements_list const& placements = helper.get();
text_render_thunk thunk(placements, halo_rasterizer, comp_op);
thunks_.push_back(std::make_shared<render_thunk>(thunk));
update_box(*common.detector_);
}
template <typename T>
void operator()(T const &) const
{
// TODO: warning if unimplemented?
}
private:
box2d<double> &box_;
render_thunk_list &thunks_;
mapnik::feature_impl &feature_;
proj_transform const &prj_trans_;
renderer_common const &common_;
text_layout const &text_;
box2d<double> clipping_extent_;
void update_box(label_collision_detector4 &detector) const
{
for (auto const &label : detector)
{
if (box_.width() > 0 && box_.height() > 0)
{
box_.expand_to_include(label.box);
}
else
{
box_ = label.box;
}
}
}
};
/**
* Render a thunk which was frozen from a previous call to
* extract_bboxes. We should now have a new offset at which
@ -273,24 +70,13 @@ struct thunk_renderer : public boost::static_visitor<>
{
text_renderer_type ren(*buf_, thunk.halo_rasterizer_, thunk.comp_op_,
common_.scale_factor_, common_.font_manager_.get_stroker());
for (glyph_positions_ptr glyphs : thunk.placements_)
{
// move the glyphs to the correct offset
pixel_position const &base_point = glyphs->get_base_point();
pixel_position new_base_point(base_point.x + offset_.x, base_point.y + offset_.y);
glyphs->set_base_point(new_base_point);
// update the position of any marker
marker_info_ptr marker_info = glyphs->marker();
if (marker_info)
{
pixel_position const &marker_pos = glyphs->marker_pos();
pixel_position new_marker_pos(marker_pos.x + offset_.x, marker_pos.y + offset_.y);
glyphs->set_marker(marker_info, new_marker_pos);
}
render_offset_placements(
thunk.placements_,
offset_,
[&] (glyph_positions_ptr glyphs) {
ren.render(*glyphs);
}
});
}
template <typename T>
@ -306,216 +92,21 @@ private:
pixel_position offset_;
};
geometry_type *origin_point(proj_transform const &prj_trans,
renderer_common const &common)
{
// note that we choose a point in the middle of the screen to
// try to ensure that we don't get edge artefacts due to any
// symbolizers with avoid-edges set: only the avoid-edges of
// the group symbolizer itself should matter.
double x = common.width_ / 2.0, y = common.height_ / 2.0, z = 0.0;
common.t_.backward(&x, &y);
prj_trans.forward(x, y, z);
geometry_type *geom = new geometry_type(geometry_type::Point);
geom->move_to(x, y);
return geom;
}
template <typename T0, typename T1>
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);
expression_attributes<std::set<std::string> > rk_attr(columns);
expression_ptr repeat_key = get<mapnik::expression_ptr>(sym, keys::repeat_key);
if (repeat_key)
render_group_symbolizer(
sym, feature, prj_trans, clipping_extent(), common_,
[&](render_thunk_list const& thunks, pixel_position const& render_offset)
{
boost::apply_visitor(rk_attr, *repeat_key);
}
// get columns from child rules and symbolizers
group_symbolizer_properties_ptr props = get<group_symbolizer_properties_ptr>(sym, keys::group_properties);
if (props) {
for (auto const& rule : props->get_rules())
{
// note that this recurses down on to the symbolizer
// internals too, so we get all free variables.
column_collector(*rule);
// still need to collect repeat key columns
if (rule->get_repeat_key())
{
boost::apply_visitor(rk_attr, *(rule->get_repeat_key()));
}
}
}
// create a new context for the sub features of this group
context_ptr sub_feature_ctx = std::make_shared<mapnik::context_type>();
// populate new context with column names referenced in the group rules and symbolizers
// and determine if any indexed columns are present
bool has_idx_cols = false;
for (auto const& col_name : columns)
{
sub_feature_ctx->push(col_name);
if (col_name.find('%') != std::string::npos)
{
has_idx_cols = true;
}
}
// keep track of the sub features that we'll want to symbolize
// along with the group rules that they matched
std::vector< std::pair<group_rule_ptr, feature_ptr> > matches;
// keep track of which lists of render thunks correspond to
// entries in the group_layout_manager.
std::vector<render_thunk_list> layout_thunks;
size_t num_layout_thunks = 0;
// layout manager to store and arrange bboxes of matched features
group_layout_manager layout_manager(props->get_layout(), pixel_position(common_.width_ / 2.0, common_.height_ / 2.0));
text_layout text(common_.font_manager_, common_.scale_factor_);
// run feature or sub feature through the group rules & symbolizers
// for each index value in the range
int start = get<value_integer>(sym, keys::start_column);
int end = start + get<value_integer>(sym, keys::num_columns);
for (int col_idx = start; col_idx < end; ++col_idx)
{
feature_ptr sub_feature;
if (has_idx_cols)
{
// create sub feature with indexed column values
sub_feature = feature_factory::create(sub_feature_ctx, col_idx);
// copy the necessary columns to sub feature
for(auto const& col_name : columns)
{
if (col_name.find('%') != std::string::npos)
{
if (col_name.size() == 1)
{
// column name is '%' by itself, so give the index as the value
sub_feature->put(col_name, (value_integer)col_idx);
}
else
{
// indexed column
std::string col_idx_name = col_name;
boost::replace_all(col_idx_name, "%", boost::lexical_cast<std::string>(col_idx));
sub_feature->put(col_name, feature.get(col_idx_name));
}
}
else
{
// non-indexed column
sub_feature->put(col_name, feature.get(col_name));
}
}
}
else
{
// no indexed columns, so use the existing feature instead of copying
sub_feature = feature_ptr(&feature);
}
// add a single point geometry at pixel origin
sub_feature->add_geometry(origin_point(prj_trans, common_));
// get the layout for this set of properties
for (auto const& rule : props->get_rules())
{
if (boost::apply_visitor(evaluate<Feature,value_type>(*sub_feature),
*(rule->get_filter())).to_bool())
{
// add matched rule and feature to the list of things to draw
matches.push_back(std::make_pair(rule, sub_feature));
// construct a bounding box around all symbolizers for the matched rule
bound_box bounds;
render_thunk_list thunks;
extract_bboxes extractor(bounds, thunks, *sub_feature, prj_trans,
common_, text, clip_box);
for (auto const& sym : *rule)
{
// TODO: construct layout and obtain bounding box
boost::apply_visitor(extractor, sym);
}
// add the bounding box to the layout manager
layout_manager.add_member_bound_box(bounds);
layout_thunks.emplace_back(std::move(thunks));
++num_layout_thunks;
break;
}
}
}
// 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);
// determine if we should be tracking repeat distance
bool check_repeat = (helper.get_properties().minimum_distance > 0);
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)
{
for (size_t layout_i = 0; layout_i < num_layout_thunks; ++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.
thunk_renderer ren(*this, current_buffer_, common_, ren_pos);
for (render_thunk_ptr &thunk : layout_thunks[layout_i])
thunk_renderer ren(*this, current_buffer_, common_, render_offset);
for (render_thunk_ptr const& thunk : thunks)
{
boost::apply_visitor(ren, *thunk);
}
}
}
});
}
template void agg_renderer<image_32>::process(group_symbolizer const&,

View file

@ -240,6 +240,7 @@ source = Split(
config_error.cpp
color_factory.cpp
renderer_common.cpp
renderer_common/process_group_symbolizer.cpp
"""
)

View file

@ -0,0 +1,153 @@
/*****************************************************************************
*
* 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
*
*****************************************************************************/
#include <mapnik/renderer_common/process_group_symbolizer.hpp>
namespace mapnik {
point_render_thunk::point_render_thunk(pixel_position const &pos, marker const &m,
agg::trans_affine const &tr, double opacity,
composite_mode_e comp_op)
: pos_(pos), marker_(std::make_shared<marker>(m)),
tr_(tr), opacity_(opacity), comp_op_(comp_op)
{}
text_render_thunk::text_render_thunk(placements_list const &placements,
composite_mode_e comp_op,
halo_rasterizer_enum halo_rasterizer)
: placements_(), glyphs_(std::make_shared<std::vector<glyph_info> >()),
comp_op_(comp_op), halo_rasterizer_(halo_rasterizer)
{
std::vector<glyph_info> &glyph_vec = *glyphs_;
size_t glyph_count = 0;
for (glyph_positions_ptr positions : placements)
{
glyph_count += std::distance(positions->begin(), positions->end());
}
glyph_vec.reserve(glyph_count);
for (glyph_positions_ptr positions : placements)
{
glyph_positions_ptr new_positions = std::make_shared<glyph_positions>();
new_positions->reserve(std::distance(positions->begin(), positions->end()));
glyph_positions &new_pos = *new_positions;
new_pos.set_base_point(positions->get_base_point());
if (positions->marker())
{
new_pos.set_marker(positions->marker(), positions->marker_pos());
}
for (glyph_position const &pos : *positions)
{
glyph_vec.push_back(*pos.glyph);
new_pos.push_back(glyph_vec.back(), pos.pos, pos.rot);
}
placements_.push_back(new_positions);
}
}
render_thunk_extractor::render_thunk_extractor(box2d<double> &box,
render_thunk_list &thunks,
mapnik::feature_impl &feature,
proj_transform const &prj_trans,
renderer_common &common,
box2d<double> const &clipping_extent)
: box_(box), thunks_(thunks), feature_(feature), prj_trans_(prj_trans),
common_(common), clipping_extent_(clipping_extent)
{}
void render_thunk_extractor::operator()(point_symbolizer const &sym) const
{
composite_mode_e comp_op = get<composite_mode_e>(sym, keys::comp_op, feature_, src_over);
render_point_symbolizer(
sym, feature_, prj_trans_, common_,
[&](pixel_position const &pos, marker const &marker,
agg::trans_affine const &tr, double opacity) {
point_render_thunk thunk(pos, marker, tr, opacity, comp_op);
thunks_.push_back(std::make_shared<render_thunk>(std::move(thunk)));
});
update_box();
}
void render_thunk_extractor::operator()(text_symbolizer const &sym) const
{
box2d<double> clip_box = clipping_extent_;
text_symbolizer_helper helper(
sym, feature_, prj_trans_,
common_.width_, common_.height_,
common_.scale_factor_,
common_.t_, common_.font_manager_, *common_.detector_,
clip_box);
composite_mode_e comp_op = get<composite_mode_e>(sym, keys::comp_op, feature_, src_over);
halo_rasterizer_enum halo_rasterizer = get<halo_rasterizer_enum>(sym, keys::halo_rasterizer, HALO_RASTERIZER_FULL);
placements_list const& placements = helper.get();
text_render_thunk thunk(placements, comp_op, halo_rasterizer);
thunks_.push_back(std::make_shared<render_thunk>(thunk));
update_box();
}
void render_thunk_extractor::update_box() const
{
label_collision_detector4 &detector = *common_.detector_;
for (auto const &label : detector)
{
if (box_.width() > 0 && box_.height() > 0)
{
box_.expand_to_include(label.box);
}
else
{
box_ = label.box;
}
}
detector.clear();
}
geometry_type *origin_point(proj_transform const &prj_trans,
renderer_common const &common)
{
// note that we choose a point in the middle of the screen to
// try to ensure that we don't get edge artefacts due to any
// symbolizers with avoid-edges set: only the avoid-edges of
// the group symbolizer itself should matter.
double x = common.width_ / 2.0, y = common.height_ / 2.0, z = 0.0;
common.t_.backward(&x, &y);
prj_trans.forward(x, y, z);
geometry_type *geom = new geometry_type(geometry_type::Point);
geom->move_to(x, y);
return geom;
}
} // namespace mapnik