Framework for group symbolizer.

This includes XML parsing of group symbolizer and related objects and
process_group_symbolizer method in the AGG renderer. This also includes
code to collect group symbolizer indexed columns, create sub features,
and match them to group rules.
This commit is contained in:
Jordan Hollinger 2013-12-19 18:11:35 -05:00
parent 269b038147
commit fbc2a0d1e3
17 changed files with 1048 additions and 57 deletions

View file

@ -25,7 +25,7 @@ from glob import glob
Import('env') Import('env')
base = './mapnik/' base = './mapnik/'
subdirs = ['','svg','wkt','grid','json','util','text','text/placements','text/formatting'] subdirs = ['','svg','wkt','grid','json','util','group','text','text/placements','text/formatting']
if env['SVG_RENDERER']: if env['SVG_RENDERER']:
subdirs.append('svg/output') subdirs.append('svg/output')

View file

@ -116,6 +116,9 @@ public:
void process(markers_symbolizer const& sym, void process(markers_symbolizer const& sym,
mapnik::feature_impl & feature, mapnik::feature_impl & feature,
proj_transform const& prj_trans); proj_transform const& prj_trans);
void process(group_symbolizer const& sym,
mapnik::feature_impl & feature,
proj_transform const& prj_trans);
void process(debug_symbolizer const& sym, void process(debug_symbolizer const& sym,
feature_impl & feature, feature_impl & feature,
proj_transform const& prj_trans); proj_transform const& prj_trans);

View file

@ -36,10 +36,14 @@
#include <mapnik/path_expression.hpp> // for path_expression_ptr #include <mapnik/path_expression.hpp> // for path_expression_ptr
#include <mapnik/text/placements/base.hpp> // for text_placements #include <mapnik/text/placements/base.hpp> // for text_placements
#include <mapnik/image_scaling.hpp> #include <mapnik/image_scaling.hpp>
#include <mapnik/group/group_symbolizer_properties.hpp>
#include <mapnik/group/group_rule.hpp>
// boost // boost
#include <boost/variant/static_visitor.hpp> #include <boost/variant/static_visitor.hpp>
#include <boost/variant/apply_visitor.hpp> #include <boost/variant/apply_visitor.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
// stl // stl
#include <set> #include <set>
@ -222,6 +226,8 @@ struct symbolizer_attributes : public boost::static_visitor<>
} }
} }
void operator () (group_symbolizer const& sym);
private: private:
std::set<std::string>& names_; std::set<std::string>& names_;
double & filter_factor_; double & filter_factor_;
@ -269,6 +275,62 @@ public:
} }
}; };
inline void symbolizer_attributes::operator () (group_symbolizer const& sym)
{
// find all column names referenced in the group symbolizer
std::set<std::string> group_columns;
attribute_collector column_collector(group_columns);
expression_attributes<std::set<std::string> > rk_attr(group_columns);
// get columns from symbolizer repeat key
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())
{
column_collector(*rule);
if (rule->get_repeat_key())
{
boost::apply_visitor(rk_attr, *(rule->get_repeat_key()));
}
}
}
// get indexed column names
for (auto const& col_name : group_columns)
{
if (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.
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)
{
std::string col_idx_name = col_name;
boost::replace_all(col_idx_name, "%", boost::lexical_cast<std::string>(col_idx));
names_.insert(col_idx_name);
}
}
}
else
{
// non indexed column name. insert as is.
names_.insert(col_name);
}
}
}
} // namespace mapnik } // namespace mapnik
#endif // MAPNIK_ATTRIBUTE_COLLECTOR_HPP #endif // MAPNIK_ATTRIBUTE_COLLECTOR_HPP

View file

@ -0,0 +1,94 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 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_GROUP_LAYOUT_HPP
#define MAPNIK_GROUP_LAYOUT_HPP
// boost
#include <boost/variant.hpp>
namespace mapnik
{
struct simple_row_layout
{
public:
simple_row_layout(double item_margin = 0.0)
: item_margin_(item_margin)
{
}
double get_item_margin() const
{
return item_margin_;
}
void set_item_margin(double item_margin)
{
item_margin_ = item_margin;
}
private:
double item_margin_;
};
struct pair_layout
{
public:
pair_layout(double item_margin = 1.0, double max_difference = - 1.0)
: item_margin_(item_margin),
max_difference_(max_difference)
{
}
double get_item_margin() const
{
return item_margin_;
}
void set_item_margin(double item_margin)
{
item_margin_ = item_margin;
}
double get_max_difference() const
{
return max_difference_;
}
void set_max_difference(double max_difference)
{
max_difference_ = max_difference;
}
private:
double item_margin_;
double max_difference_;
};
typedef boost::variant<simple_row_layout,
pair_layout> group_layout;
typedef std::shared_ptr<group_layout> group_layout_ptr;
}
#endif // MAPNIK_GROUP_LAYOUT_HPP

View file

@ -0,0 +1,104 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 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_GROUP_LAYOUT_MANAGER_HPP
#define MAPNIK_GROUP_LAYOUT_MANAGER_HPP
// mapnik
#include <mapnik/pixel_position.hpp>
#include <mapnik/box2d.hpp>
#include <mapnik/group/group_layout.hpp>
// stl
#include <vector>
using std::vector;
namespace mapnik
{
typedef box2d<double> bound_box;
struct group_layout_manager
{
group_layout_manager(const group_layout &layout)
: layout_(layout),
input_origin_(0, 0),
member_boxes_(vector<bound_box>()),
member_offsets_(vector<pixel_position>()),
update_layout_(true)
{
}
group_layout_manager(const group_layout &layout, const pixel_position &input_origin)
: layout_(layout),
input_origin_(input_origin),
member_boxes_(vector<bound_box>()),
member_offsets_(vector<pixel_position>()),
update_layout_(true)
{
}
group_layout_manager(const group_layout &layout, const pixel_position &input_origin,
const vector<bound_box> &item_boxes)
: layout_(layout),
input_origin_(input_origin),
member_boxes_(item_boxes),
member_offsets_(vector<pixel_position>()),
update_layout_(true)
{
}
inline void set_layout(const group_layout &layout)
{
layout_ = layout;
update_layout_ = true;
}
inline void add_member_bound_box(const bound_box &member_box)
{
member_boxes_.push_back(member_box);
update_layout_ = true;
}
inline const pixel_position &offset_at(size_t i)
{
handle_update();
return member_offsets_.at(i);
}
bound_box offset_box_at(size_t i);
private:
void handle_update();
group_layout layout_;
pixel_position input_origin_;
vector<bound_box> member_boxes_;
vector<pixel_position> member_offsets_;
bool update_layout_;
};
} // namespace mapnik
#endif // MAPNIK_GROUP_LAYOUT_MANAGER_HPP

View file

@ -0,0 +1,103 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 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_GROUP_RULE_HPP
#define MAPNIK_GROUP_RULE_HPP
// mapnik
#include <mapnik/config.hpp>
#include <mapnik/symbolizer.hpp>
#include <mapnik/expression.hpp>
// boost
#include <boost/shared_ptr.hpp>
namespace mapnik
{
/**
* group rule contains a set of symbolizers which should
* be rendered atomically when the filter attached to
* this rule is matched.
*/
struct group_rule
{
typedef std::vector<symbolizer> symbolizers;
explicit group_rule(const expression_ptr& filter, const expression_ptr& repeat_key);
group_rule &operator=(const group_rule &rhs);
bool operator==(const group_rule &rhs) const;
void append(const symbolizer &);
const symbolizers &get_symbolizers() const
{
return symbolizers_;
}
inline symbolizers::const_iterator begin() const
{
return symbolizers_.begin();
}
inline symbolizers::const_iterator end() const
{
return symbolizers_.end();
}
inline void set_filter(const expression_ptr& filter)
{
filter_ = filter;
}
inline expression_ptr const& get_filter() const
{
return filter_;
}
inline void set_repeat_key(const expression_ptr& repeat_key)
{
repeat_key_ = repeat_key;
}
inline expression_ptr const& get_repeat_key() const
{
return repeat_key_;
}
private:
// expression filter - when data matches this then
// the symbolizers should be drawn.
expression_ptr filter_;
// expression repeat key - repeat key to be used with minimum distance
expression_ptr repeat_key_;
// the atomic set of symbolizers
symbolizers symbolizers_;
};
}
#endif // MAPNIK_GROUP_RULE_HPP

View file

@ -0,0 +1,62 @@
/*****************************************************************************
*
* 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_PROPERTIES_HPP
#define GROUP_PROPERTIES_HPP
// mapnik
#include <mapnik/config.hpp>
#include <mapnik/group/group_layout.hpp>
// stl
#include <vector>
namespace mapnik
{
struct group_rule;
typedef std::shared_ptr<group_rule> group_rule_ptr;
typedef std::vector<group_rule_ptr> group_rules;
/** Contains all group symbolizer properties related to building a group layout. */
struct group_symbolizer_properties
{
inline group_symbolizer_properties() : rules_() { }
/** Load all values from XML ptree. */
//void from_xml(xml_node const &sym, fontset_map const & fontsets);
/** Get the layout. */
inline group_layout const& get_layout() const { return layout_; }
/** Get the group rules. */
inline group_rules const& get_rules() const { return rules_; }
/** Set the layout. */
inline void set_layout (group_layout && layout) { layout_ = std::move(layout); }
/** Add add group rule. */
inline void add_rule (group_rule_ptr rule) { rules_.push_back(rule); }
private:
group_layout layout_;
group_rules rules_;
};
typedef std::shared_ptr<group_symbolizer_properties> group_symbolizer_properties_ptr;
} //ns mapnik
#endif // GROUP_PROPERTIES_HPP

View file

@ -37,6 +37,7 @@
#include <mapnik/text/placements/base.hpp> #include <mapnik/text/placements/base.hpp>
#include <mapnik/text/placements/dummy.hpp> #include <mapnik/text/placements/dummy.hpp>
#include <mapnik/raster_colorizer.hpp> #include <mapnik/raster_colorizer.hpp>
#include <mapnik/group/group_symbolizer_properties.hpp>
// stl // stl
#include <type_traits> #include <type_traits>
#include <algorithm> #include <algorithm>
@ -78,7 +79,6 @@ struct enumeration_wrapper
} }
}; };
typedef std::vector<std::pair<double,double> > dash_array; typedef std::vector<std::pair<double,double> > dash_array;
struct MAPNIK_DECL symbolizer_base struct MAPNIK_DECL symbolizer_base
@ -94,7 +94,8 @@ struct MAPNIK_DECL symbolizer_base
transform_type, transform_type,
text_placements_ptr, text_placements_ptr,
dash_array, dash_array,
raster_colorizer_ptr> value_type; raster_colorizer_ptr,
group_symbolizer_properties_ptr> value_type;
typedef mapnik::keys key_type; typedef mapnik::keys key_type;
typedef std::map<key_type, value_type> cont_type; typedef std::map<key_type, value_type> cont_type;
cont_type properties; cont_type properties;
@ -121,7 +122,9 @@ enum class property_types : std::uint8_t
target_transform, target_transform,
target_placement, target_placement,
target_dash_array, target_dash_array,
target_colorizer target_colorizer,
target_repeat_key,
target_group_symbolizer_properties
}; };
inline bool operator==(symbolizer_base const& lhs, symbolizer_base const& rhs) inline bool operator==(symbolizer_base const& lhs, symbolizer_base const& rhs)
@ -415,6 +418,7 @@ struct MAPNIK_DECL polygon_pattern_symbolizer : public symbolizer_base {};
struct MAPNIK_DECL markers_symbolizer : public symbolizer_base {}; struct MAPNIK_DECL markers_symbolizer : public symbolizer_base {};
struct MAPNIK_DECL raster_symbolizer : public symbolizer_base {}; struct MAPNIK_DECL raster_symbolizer : public symbolizer_base {};
struct MAPNIK_DECL building_symbolizer : public symbolizer_base {}; struct MAPNIK_DECL building_symbolizer : public symbolizer_base {};
struct MAPNIK_DECL group_symbolizer : public symbolizer_base {};
struct MAPNIK_DECL debug_symbolizer : public symbolizer_base {}; struct MAPNIK_DECL debug_symbolizer : public symbolizer_base {};
// symbolizer // symbolizer
@ -428,6 +432,7 @@ typedef boost::variant<point_symbolizer,
text_symbolizer, text_symbolizer,
building_symbolizer, building_symbolizer,
markers_symbolizer, markers_symbolizer,
group_symbolizer,
debug_symbolizer> symbolizer; debug_symbolizer> symbolizer;

View file

@ -78,6 +78,10 @@ enum class keys : std::uint8_t
point_placement_type, point_placement_type,
colorizer, colorizer,
halo_transform, halo_transform,
num_columns,
start_column,
repeat_key,
group_properties,
MAX_SYMBOLIZER_KEY MAX_SYMBOLIZER_KEY
}; };

View file

@ -165,6 +165,8 @@ class text_layout;
struct MAPNIK_DECL text_symbolizer_properties struct MAPNIK_DECL text_symbolizer_properties
{ {
text_symbolizer_properties(); text_symbolizer_properties();
/** Load only placement related values from XML ptree. */
void placement_properties_from_xml(xml_node const &sym);
/** Load all values from XML ptree. */ /** Load all values from XML ptree. */
void from_xml(xml_node const &sym, fontset_map const & fontsets); void from_xml(xml_node const &sym, fontset_map const & fontsets);
/** Save all values to XML ptree (but does not create a new parent node!). */ /** Save all values to XML ptree (but does not create a new parent node!). */

View file

@ -0,0 +1,175 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2011 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
// mapnik
#include <mapnik/feature.hpp>
#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/layout.hpp>
#include <mapnik/group/group_layout_manager.hpp>
#include <mapnik/geom_util.hpp>
#include <mapnik/symbolizer.hpp>
#include <mapnik/parse_path.hpp>
#include <mapnik/pixel_position.hpp>
// agg
#include "agg_trans_affine.h"
// stl
#include <string>
// boost
#include <boost/variant/apply_visitor.hpp>
namespace mapnik {
template <typename T0, typename T1>
void agg_renderer<T0,T1>::process(group_symbolizer const& sym,
mapnik::feature_impl & feature,
proj_transform const& prj_trans)
{
// 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())
{
// 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;
// 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);
}
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;
for (auto const& sym : *rule)
{
// TODO: construct layout and obtain bounding box
}
// add the bounding box to the layout manager
layout_manager.add_member_bound_box(bounds);
break;
}
}
}
}
template void agg_renderer<image_32>::process(group_symbolizer const&,
mapnik::feature_impl &,
proj_transform const&);
}

View file

@ -233,6 +233,8 @@ source = Split(
text/placements/dummy.cpp text/placements/dummy.cpp
text/placements/list.cpp text/placements/list.cpp
text/placements/simple.cpp text/placements/simple.cpp
group/group_layout_manager.cpp
group/group_rule.cpp
xml_tree.cpp xml_tree.cpp
config_error.cpp config_error.cpp
color_factory.cpp color_factory.cpp
@ -297,6 +299,7 @@ source += Split(
agg/process_raster_symbolizer.cpp agg/process_raster_symbolizer.cpp
agg/process_shield_symbolizer.cpp agg/process_shield_symbolizer.cpp
agg/process_markers_symbolizer.cpp agg/process_markers_symbolizer.cpp
agg/process_group_symbolizer.cpp
agg/process_debug_symbolizer.cpp agg/process_debug_symbolizer.cpp
""" """
) )

View file

@ -0,0 +1,171 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 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_layout_manager.hpp>
// boost
#include <boost/variant.hpp>
// std
#include <cmath>
namespace mapnik
{
// This visitor will process offsets for the given layout
struct process_layout : public boost::static_visitor<>
{
// The vector containing the existing, centered item bounding boxes
const vector<bound_box> &member_boxes_;
// The vector to populate with item offsets
vector<pixel_position> &member_offsets_;
// The origin point of the member boxes
// i.e. The member boxes are positioned around input_origin,
// and the offset values should position them around (0,0)
const pixel_position &input_origin_;
process_layout(const vector<bound_box> &member_bboxes,
vector<pixel_position> &member_offsets,
const pixel_position &input_origin)
: member_boxes_(member_bboxes),
member_offsets_(member_offsets),
input_origin_(input_origin)
{
}
// arrange group memebers in centered, horizontal row
void operator()(simple_row_layout const& layout) const
{
double total_width = (member_boxes_.size() - 1) * layout.get_item_margin();
for (auto const& box : member_boxes_)
{
total_width += box.width();
}
double x_offset = -(total_width / 2.0);
for (auto const& box : member_boxes_)
{
member_offsets_.push_back(pixel_position(x_offset - box.minx(), -input_origin_.y));
x_offset += box.width() + layout.get_item_margin();
}
}
// arrange group members in x horizontal pairs of 2,
// one to the left and one to the right of center in each pair
void operator()(pair_layout const& layout) const
{
member_offsets_.resize(member_boxes_.size());
double y_margin = layout.get_item_margin();
double x_margin = y_margin / 2.0;
if (member_boxes_.size() == 1)
{
member_offsets_[0] = pixel_position(0, 0) - input_origin_;
return;
}
bound_box layout_box;
size_t middle_ifirst = (member_boxes_.size() - 1) >> 1, top_i = 0, bottom_i = 0;
if (middle_ifirst % 2 == 0)
{
layout_box = make_horiz_pair(0, 0.0, 0, x_margin, layout.get_max_difference());
top_i = middle_ifirst - 2;
bottom_i = middle_ifirst + 2;
}
else
{
top_i = middle_ifirst - 1;
bottom_i = middle_ifirst + 1;
}
while (bottom_i >= 0 && top_i < member_offsets_.size())
{
layout_box.expand_to_include(make_horiz_pair(top_i, layout_box.miny() - y_margin, -1, x_margin, layout.get_max_difference()));
layout_box.expand_to_include(make_horiz_pair(bottom_i, layout_box.maxy() + y_margin, 1, x_margin, layout.get_max_difference()));
top_i -= 2;
bottom_i += 2;
}
}
private:
// Place member bound boxes at [ifirst] and [ifirst + 1] in a horizontal pairi, vertically
// align with pair_y, store corresponding offsets, and return bound box of combined pair
// Note: x_margin is the distance between box edge and x center
bound_box make_horiz_pair(size_t ifirst, double pair_y, int y_dir, double x_margin, double max_diff) const
{
// two boxes available for pair
if (ifirst + 1 < member_boxes_.size())
{
double x_center = member_boxes_[ifirst].width() - member_boxes_[ifirst + 1].width();
if (max_diff < 0.0 || std::abs(x_center) <= max_diff)
{
x_center = 0.0;
}
bound_box pair_box = box_offset_align(ifirst, x_center - x_margin, pair_y, -1, y_dir);
pair_box.expand_to_include(box_offset_align(ifirst + 1, x_center + x_margin, pair_y, 1, y_dir));
return pair_box;
}
// only one box available for this "pair", so keep x-centered and handle y-offset
return box_offset_align(ifirst, 0, pair_y, 0, y_dir);
}
// Offsets member bound box at [i] and align with (x, y), in direction <x_dir, y_dir>
// stores corresponding offset, and returns modified bounding box
bound_box box_offset_align(size_t i, double x, double y, int x_dir, int y_dir) const
{
const bound_box &box = member_boxes_[i];
pixel_position offset((x_dir == 0 ? x - input_origin_.x : x - (x_dir < 0 ? box.maxx() : box.minx())),
(y_dir == 0 ? y - input_origin_.y : y - (y_dir < 0 ? box.maxy() : box.miny())));
member_offsets_[i] = offset;
return bound_box(box.minx() + offset.x, box.miny() + offset.y, box.maxx() + offset.x, box.maxy() + offset.y);
}
};
bound_box group_layout_manager::offset_box_at(size_t i)
{
handle_update();
const pixel_position &offset = member_offsets_.at(i);
const bound_box &box = member_boxes_.at(i);
return box2d<double>(box.minx() + offset.x, box.miny() + offset.y,
box.maxx() + offset.x, box.maxy() + offset.y);
}
void group_layout_manager::handle_update()
{
if (update_layout_)
{
member_offsets_.clear();
boost::apply_visitor(process_layout(member_boxes_, member_offsets_, input_origin_), layout_);
update_layout_ = false;
}
}
}

59
src/group/group_rule.cpp Normal file
View file

@ -0,0 +1,59 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 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/symbolizer.hpp>
#include <mapnik/group/group_rule.hpp>
// stl
#include <iostream>
namespace mapnik
{
group_rule::group_rule(const expression_ptr& filter = std::make_shared<mapnik::expr_node>(true),
const expression_ptr& repeat_key = expression_ptr())
: filter_(filter),
repeat_key_(repeat_key)
{
}
group_rule &group_rule::operator=(const group_rule &rhs)
{
group_rule tmp(rhs);
filter_.swap(tmp.filter_);
symbolizers_.swap(tmp.symbolizers_);
return *this;
}
bool group_rule::operator==(const group_rule &rhs) const
{
return (this == &rhs);
}
void group_rule::append(const mapnik::symbolizer &sym)
{
symbolizers_.push_back(sym);
}
}

View file

@ -54,6 +54,7 @@
#include <mapnik/util/fs.hpp> #include <mapnik/util/fs.hpp>
#include <mapnik/image_filter_types.hpp> #include <mapnik/image_filter_types.hpp>
#include <mapnik/projection.hpp> #include <mapnik/projection.hpp>
#include <mapnik/group/group_rule.hpp>
// boost // boost
@ -101,6 +102,7 @@ private:
void parse_rule(feature_type_style & style, xml_node const & r); void parse_rule(feature_type_style & style, xml_node const & r);
void parse_symbolizers(rule & rule, xml_node const & node);
void parse_point_symbolizer(rule & rule, xml_node const& sym); void parse_point_symbolizer(rule & rule, xml_node const& sym);
void parse_line_pattern_symbolizer(rule & rule, xml_node const& sym); void parse_line_pattern_symbolizer(rule & rule, xml_node const& sym);
void parse_polygon_pattern_symbolizer(rule & rule, xml_node const& sym); void parse_polygon_pattern_symbolizer(rule & rule, xml_node const& sym);
@ -111,7 +113,13 @@ private:
void parse_building_symbolizer(rule & rule, xml_node const& sym); void parse_building_symbolizer(rule & rule, xml_node const& sym);
void parse_raster_symbolizer(rule & rule, xml_node const& sym); void parse_raster_symbolizer(rule & rule, xml_node const& sym);
void parse_markers_symbolizer(rule & rule, xml_node const& sym); void parse_markers_symbolizer(rule & rule, xml_node const& sym);
void parse_group_symbolizer(rule &rule, xml_node const& sym);
void parse_debug_symbolizer(rule & rule, xml_node const& sym); void parse_debug_symbolizer(rule & rule, xml_node const& sym);
void parse_group_rule(group_symbolizer_properties &prop, xml_node const &r);
void parse_simple_layout(group_symbolizer_properties &prop, xml_node const &node);
void parse_pair_layout(group_symbolizer_properties &prop, xml_node const &nd);
bool parse_raster_colorizer(raster_colorizer_ptr const& rc, xml_node const& node); bool parse_raster_colorizer(raster_colorizer_ptr const& rc, xml_node const& node);
void parse_stroke(symbolizer_base & symbol, xml_node const & sym); void parse_stroke(symbolizer_base & symbol, xml_node const & sym);
void ensure_font_face(std::string const& face_name); void ensure_font_face(std::string const& face_name);
@ -792,6 +800,22 @@ void map_parser::parse_rule(feature_type_style & style, xml_node const& node)
rule.set_max_scale(child->get_value<double>()); rule.set_max_scale(child->get_value<double>());
} }
parse_symbolizers(rule, node);
style.add_rule(rule);
}
catch (config_error const& ex)
{
if (!name.empty())
{
ex.append_context(std::string("in rule '") + name + "'", node);
}
throw;
}
}
void map_parser::parse_symbolizers(rule & rule, xml_node const & node)
{
for (auto const& sym_node : node) for (auto const& sym_node : node)
{ {
switch (name2int(sym_node.name().c_str())) switch (name2int(sym_node.name().c_str()))
@ -836,6 +860,10 @@ void map_parser::parse_rule(feature_type_style & style, xml_node const& node)
parse_markers_symbolizer(rule, sym_node); parse_markers_symbolizer(rule, sym_node);
sym_node.set_processed(true); sym_node.set_processed(true);
break; break;
case name2int("GroupSymbolizer"):
parse_group_symbolizer(rule, sym_node);
sym_node.set_processed(true);
break;
case name2int("DebugSymbolizer"): case name2int("DebugSymbolizer"):
parse_debug_symbolizer(rule, sym_node); parse_debug_symbolizer(rule, sym_node);
sym_node.set_processed(true); sym_node.set_processed(true);
@ -844,17 +872,6 @@ void map_parser::parse_rule(feature_type_style & style, xml_node const& node)
break; break;
} }
} }
style.add_rule(rule);
}
catch (config_error const& ex)
{
if (!name.empty())
{
ex.append_context(std::string("in rule '") + name + "'", node);
}
throw;
}
} }
// helper method // helper method
@ -1552,6 +1569,58 @@ void map_parser::parse_raster_symbolizer(rule & rule, xml_node const & sym)
} }
} }
void map_parser::parse_group_symbolizer(rule &rule, xml_node const & sym)
{
try
{
group_symbolizer symbol;
group_symbolizer_properties_ptr prop = std::make_shared<group_symbolizer_properties>();
set_symbolizer_property<symbolizer_base, value_integer>(symbol, keys::num_columns, sym);
set_symbolizer_property<symbolizer_base, value_integer>(symbol, keys::start_column, sym);
set_symbolizer_property<symbolizer_base, expression_ptr>(symbol, keys::repeat_key, sym);
text_placements_ptr placements = std::make_shared<text_placements_dummy>();
placements->defaults.placement_properties_from_xml(sym);
put<text_placements_ptr>(symbol, keys::text_placements_, placements);
size_t layout_count = 0;
for (auto const& node : sym)
{
if (node.is("GroupRule"))
{
parse_group_rule(*prop, node);
node.set_processed(true);
}
else if (node.is("SimpleLayout"))
{
parse_simple_layout(*prop, node);
node.set_processed(true);
++layout_count;
}
else if (node.is("PairLayout"))
{
parse_pair_layout(*prop, node);
node.set_processed(true);
++layout_count;
}
if (layout_count > 1)
{
throw config_error("Provide only one layout for a GroupSymbolizer.");
}
}
put(symbol, keys::group_properties, prop);
parse_symbolizer_base(symbol, sym);
rule.append(symbol);
}
catch (const config_error & ex)
{
ex.append_context(sym);
throw;
}
}
void map_parser::parse_debug_symbolizer(rule & rule, xml_node const & sym) void map_parser::parse_debug_symbolizer(rule & rule, xml_node const & sym)
{ {
debug_symbolizer symbol; debug_symbolizer symbol;
@ -1650,6 +1719,71 @@ bool map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc,
return found_stops; return found_stops;
} }
void map_parser::parse_group_rule(group_symbolizer_properties & prop, xml_node const & node)
{
try
{
rule fake_rule;
expression_ptr filter, repeat_key;
xml_node const *filter_child = node.get_opt_child("Filter"),
*rptkey_child = node.get_opt_child("RepeatKey");
if (filter_child)
{
filter = filter_child->get_value<expression_ptr>();
}
else
{
filter = std::make_shared<mapnik::expr_node>(true);
}
if (rptkey_child)
{
repeat_key = rptkey_child->get_value<expression_ptr>();
}
group_rule_ptr rule = std::make_shared<group_rule>(filter, repeat_key);
parse_symbolizers(fake_rule, node);
for (auto const& sym : fake_rule)
{
rule->append(sym);
}
prop.add_rule(rule);
}
catch (const config_error & ex)
{
ex.append_context(node);
throw;
}
}
void map_parser::parse_simple_layout(group_symbolizer_properties & prop, xml_node const & node)
{
simple_row_layout layout;
optional<double> item_margin = node.get_opt_attr<double>("item-margin");
if (item_margin) layout.set_item_margin(*item_margin);
prop.set_layout(std::move(layout));
}
void map_parser::parse_pair_layout(group_symbolizer_properties & prop, xml_node const & node)
{
pair_layout layout;
optional<double> item_margin = node.get_opt_attr<double>("item-margin");
if (item_margin) layout.set_item_margin(*item_margin);
optional<double> max_difference = node.get_opt_attr<double>("max-difference");
if (max_difference) layout.set_max_difference(*max_difference);
prop.set_layout(std::move(layout));
}
void map_parser::ensure_font_face(std::string const& face_name) void map_parser::ensure_font_face(std::string const& face_name)
{ {
if (! font_manager_.get_face(face_name)) if (! font_manager_.get_face(face_name))

View file

@ -91,6 +91,10 @@ static const property_meta_type key_meta[to_integral(keys::MAX_SYMBOLIZER_KEY)]
[](enumeration_wrapper e) { return enumeration<point_placement_enum,point_placement_enum_MAX>(point_placement_enum(e.value)).as_string();}, property_types::target_double }, [](enumeration_wrapper e) { return enumeration<point_placement_enum,point_placement_enum_MAX>(point_placement_enum(e.value)).as_string();}, property_types::target_double },
property_meta_type{ "raster-colorizer", nullptr, nullptr, property_types::target_colorizer}, property_meta_type{ "raster-colorizer", nullptr, nullptr, property_types::target_colorizer},
property_meta_type{ "halo-transform", false, nullptr, property_types::target_transform }, property_meta_type{ "halo-transform", false, nullptr, property_types::target_transform },
property_meta_type{ "num-columns", static_cast<value_integer>(0), nullptr, property_types::target_integer},
property_meta_type{ "start-column", static_cast<value_integer>(1), nullptr, property_types::target_integer},
property_meta_type{ "repeat-key", nullptr, nullptr, property_types::target_repeat_key},
property_meta_type{ "symbolizer-properties", nullptr, nullptr, property_types::target_group_symbolizer_properties}
}; };
property_meta_type const& get_meta(mapnik::keys key) property_meta_type const& get_meta(mapnik::keys key)

View file

@ -138,7 +138,7 @@ formatting::node_ptr text_symbolizer_properties::format_tree() const
return tree_; return tree_;
} }
void text_symbolizer_properties::from_xml(xml_node const &sym, fontset_map const & fontsets) void text_symbolizer_properties::placement_properties_from_xml(xml_node const &sym)
{ {
optional<label_placement_e> placement_ = sym.get_opt_attr<label_placement_e>("placement"); optional<label_placement_e> placement_ = sym.get_opt_attr<label_placement_e>("placement");
if (placement_) label_placement = *placement_; if (placement_) label_placement = *placement_;
@ -163,6 +163,12 @@ void text_symbolizer_properties::from_xml(xml_node const &sym, fontset_map const
if (allow_overlap_) allow_overlap = *allow_overlap_; if (allow_overlap_) allow_overlap = *allow_overlap_;
optional<boolean> largest_bbox_only_ = sym.get_opt_attr<boolean>("largest-bbox-only"); optional<boolean> largest_bbox_only_ = sym.get_opt_attr<boolean>("largest-bbox-only");
if (largest_bbox_only_) largest_bbox_only = *largest_bbox_only_; if (largest_bbox_only_) largest_bbox_only = *largest_bbox_only_;
}
void text_symbolizer_properties::from_xml(xml_node const &sym, fontset_map const & fontsets)
{
placement_properties_from_xml(sym);
optional<double> max_char_angle_delta_ = sym.get_opt_attr<double>("max-char-angle-delta"); optional<double> max_char_angle_delta_ = sym.get_opt_attr<double>("max-char-angle-delta");
if (max_char_angle_delta_) max_char_angle_delta=(*max_char_angle_delta_)*(M_PI/180); if (max_char_angle_delta_) max_char_angle_delta=(*max_char_angle_delta_)*(M_PI/180);
optional<text_upright_e> upright_ = sym.get_opt_attr<text_upright_e>("upright"); optional<text_upright_e> upright_ = sym.get_opt_attr<text_upright_e>("upright");