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:
parent
efc29649ab
commit
3d1c30db1e
6 changed files with 539 additions and 428 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
339
include/mapnik/renderer_common/process_group_symbolizer.hpp
Normal file
339
include/mapnik/renderer_common/process_group_symbolizer.hpp
Normal 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 */
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
ren.render(*glyphs);
|
||||
}
|
||||
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)
|
||||
{
|
||||
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())
|
||||
render_group_symbolizer(
|
||||
sym, feature, prj_trans, clipping_extent(), common_,
|
||||
[&](render_thunk_list const& thunks, pixel_position const& render_offset)
|
||||
{
|
||||
// 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&,
|
||||
|
|
|
@ -240,6 +240,7 @@ source = Split(
|
|||
config_error.cpp
|
||||
color_factory.cpp
|
||||
renderer_common.cpp
|
||||
renderer_common/process_group_symbolizer.cpp
|
||||
"""
|
||||
)
|
||||
|
||||
|
|
153
src/renderer_common/process_group_symbolizer.cpp
Normal file
153
src/renderer_common/process_group_symbolizer.cpp
Normal 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
|
Loading…
Reference in a new issue