diff --git a/include/mapnik/renderer_common.hpp b/include/mapnik/renderer_common.hpp index ecf90d007..62faab3b7 100644 --- a/include/mapnik/renderer_common.hpp +++ b/include/mapnik/renderer_common.hpp @@ -46,6 +46,7 @@ struct renderer_common std::shared_ptr detector); renderer_common(request const &req, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height, double scale_factor); + renderer_common(const renderer_common &); unsigned width_; unsigned height_; diff --git a/include/mapnik/renderer_common/process_point_symbolizer.hpp b/include/mapnik/renderer_common/process_point_symbolizer.hpp index 1e97675d0..e5308da92 100644 --- a/include/mapnik/renderer_common/process_point_symbolizer.hpp +++ b/include/mapnik/renderer_common/process_point_symbolizer.hpp @@ -23,6 +23,9 @@ #ifndef MAPNIK_RENDERER_COMMON_PROCESS_POINT_SYMBOLIZER_HPP #define MAPNIK_RENDERER_COMMON_PROCESS_POINT_SYMBOLIZER_HPP +#include +#include + namespace mapnik { template diff --git a/src/agg/process_group_symbolizer.cpp b/src/agg/process_group_symbolizer.cpp index 25022ad32..7f491d9e7 100644 --- a/src/agg/process_group_symbolizer.cpp +++ b/src/agg/process_group_symbolizer.cpp @@ -29,11 +29,16 @@ #include #include #include +#include #include #include #include #include +#include + +#include +#include // agg #include "agg_trans_affine.h" @@ -46,6 +51,168 @@ 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(m)), + tr_(tr), opacity_(opacity), comp_op_(comp_op) + {} +}; + +struct text_render_thunk +{ + placements_list placements_; + halo_rasterizer_enum halo_rasterizer_; + composite_mode_e comp_op_; + + text_render_thunk(placements_list const &placements, + halo_rasterizer_enum halo_rasterizer, + composite_mode_e comp_op) + : placements_(placements), halo_rasterizer_(halo_rasterizer), + comp_op_(comp_op) + {} +}; + +// Variant type for render thunks to allow us to re-render them +// via a static visitor later. +typedef boost::variant render_thunk; +typedef std::shared_ptr render_thunk_ptr; +typedef std::list 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 &box, + render_thunk_list &thunks, + mapnik::feature_impl &feature, + proj_transform const &prj_trans, + renderer_common const &common, + text_layout const &text, + box2d 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_->clear(); + + composite_mode_e comp_op = get(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(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_->clear(); + + box2d 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(sym, keys::halo_rasterizer, HALO_RASTERIZER_FULL); + composite_mode_e comp_op = get(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(std::move(thunk))); + + update_box(*common.detector_); + } + + template + void operator()(T const &sym) const + { + // TODO: warning if unimplemented? + } + +private: + box2d &box_; + render_thunk_list &thunks_; + mapnik::feature_impl &feature_; + proj_transform const &prj_trans_; + renderer_common const &common_; + text_layout const &text_; + box2d clipping_extent_; + + void update_box(label_collision_detector4 &detector) const + { + for (auto const &label : detector) + { + box_.expand_to_include(label.box); + } + } +}; + +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 void agg_renderer::process(group_symbolizer const& sym, mapnik::feature_impl & feature, @@ -145,6 +312,12 @@ void agg_renderer::process(group_symbolizer const& sym, 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 + group_layout const &layout = props->get_layout(); + for (auto const& rule : props->get_rules()) { if (boost::apply_visitor(evaluate(*sub_feature), @@ -155,9 +328,14 @@ void agg_renderer::process(group_symbolizer const& sym, // 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, 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 diff --git a/src/renderer_common.cpp b/src/renderer_common.cpp index 9a3e4b475..e0f437ff6 100644 --- a/src/renderer_common.cpp +++ b/src/renderer_common.cpp @@ -66,4 +66,9 @@ renderer_common::renderer_common(request const &req, unsigned offset_x, unsigned req.width() + req.buffer_size() ,req.height() + req.buffer_size()))) {} +renderer_common::renderer_common(renderer_common const &other) + : renderer_common(other.width_, other.height_, other.scale_factor_, + CoordTransform(other.t_), other.detector_) +{} + }