From fbc2a0d1e347d6e28ed0a1d431fe918c1daae5a0 Mon Sep 17 00:00:00 2001 From: Jordan Hollinger Date: Thu, 19 Dec 2013 18:11:35 -0500 Subject: [PATCH] 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. --- include/build.py | 2 +- include/mapnik/agg_renderer.hpp | 3 + include/mapnik/attribute_collector.hpp | 62 +++++ include/mapnik/group/group_layout.hpp | 94 +++++++ include/mapnik/group/group_layout_manager.hpp | 104 ++++++++ include/mapnik/group/group_rule.hpp | 103 ++++++++ .../group/group_symbolizer_properties.hpp | 62 +++++ include/mapnik/symbolizer.hpp | 11 +- include/mapnik/symbolizer_keys.hpp | 4 + include/mapnik/text/text_properties.hpp | 2 + src/agg/process_group_symbolizer.cpp | 175 +++++++++++++ src/build.py | 3 + src/group/group_layout_manager.cpp | 171 +++++++++++++ src/group/group_rule.cpp | 59 +++++ src/load_map.cpp | 238 ++++++++++++++---- src/symbolizer_keys.cpp | 4 + src/text/text_properties.cpp | 8 +- 17 files changed, 1048 insertions(+), 57 deletions(-) create mode 100644 include/mapnik/group/group_layout.hpp create mode 100644 include/mapnik/group/group_layout_manager.hpp create mode 100644 include/mapnik/group/group_rule.hpp create mode 100644 include/mapnik/group/group_symbolizer_properties.hpp create mode 100644 src/agg/process_group_symbolizer.cpp create mode 100644 src/group/group_layout_manager.cpp create mode 100644 src/group/group_rule.cpp diff --git a/include/build.py b/include/build.py index 2e1954db5..ab164c84f 100644 --- a/include/build.py +++ b/include/build.py @@ -25,7 +25,7 @@ from glob import glob Import('env') 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']: subdirs.append('svg/output') diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index b14ac8d6c..5e6e43dd9 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -116,6 +116,9 @@ public: void process(markers_symbolizer const& sym, mapnik::feature_impl & feature, 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, feature_impl & feature, proj_transform const& prj_trans); diff --git a/include/mapnik/attribute_collector.hpp b/include/mapnik/attribute_collector.hpp index 2a3df94d1..ac3dbc250 100644 --- a/include/mapnik/attribute_collector.hpp +++ b/include/mapnik/attribute_collector.hpp @@ -36,10 +36,14 @@ #include // for path_expression_ptr #include // for text_placements #include +#include +#include // boost #include #include +#include +#include // stl #include @@ -222,6 +226,8 @@ struct symbolizer_attributes : public boost::static_visitor<> } } + void operator () (group_symbolizer const& sym); + private: std::set& names_; 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 group_columns; + attribute_collector column_collector(group_columns); + expression_attributes > rk_attr(group_columns); + + // get columns from symbolizer repeat key + expression_ptr repeat_key = get(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(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(sym, keys::start_column); + int end = start + get(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(col_idx)); + names_.insert(col_idx_name); + } + } + } + else + { + // non indexed column name. insert as is. + names_.insert(col_name); + } + } +} + } // namespace mapnik #endif // MAPNIK_ATTRIBUTE_COLLECTOR_HPP diff --git a/include/mapnik/group/group_layout.hpp b/include/mapnik/group/group_layout.hpp new file mode 100644 index 000000000..11de0aaa3 --- /dev/null +++ b/include/mapnik/group/group_layout.hpp @@ -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 + +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 group_layout; + +typedef std::shared_ptr group_layout_ptr; +} + +#endif // MAPNIK_GROUP_LAYOUT_HPP diff --git a/include/mapnik/group/group_layout_manager.hpp b/include/mapnik/group/group_layout_manager.hpp new file mode 100644 index 000000000..570346e06 --- /dev/null +++ b/include/mapnik/group/group_layout_manager.hpp @@ -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 +#include +#include + +// stl +#include + +using std::vector; + +namespace mapnik +{ + +typedef box2d bound_box; + +struct group_layout_manager +{ + group_layout_manager(const group_layout &layout) + : layout_(layout), + input_origin_(0, 0), + member_boxes_(vector()), + member_offsets_(vector()), + update_layout_(true) + { + } + + group_layout_manager(const group_layout &layout, const pixel_position &input_origin) + : layout_(layout), + input_origin_(input_origin), + member_boxes_(vector()), + member_offsets_(vector()), + update_layout_(true) + { + } + + group_layout_manager(const group_layout &layout, const pixel_position &input_origin, + const vector &item_boxes) + : layout_(layout), + input_origin_(input_origin), + member_boxes_(item_boxes), + member_offsets_(vector()), + 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 member_boxes_; + vector member_offsets_; + bool update_layout_; +}; + +} // namespace mapnik + +#endif // MAPNIK_GROUP_LAYOUT_MANAGER_HPP diff --git a/include/mapnik/group/group_rule.hpp b/include/mapnik/group/group_rule.hpp new file mode 100644 index 000000000..171aba2ca --- /dev/null +++ b/include/mapnik/group/group_rule.hpp @@ -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 +#include +#include + +// boost +#include + +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 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 diff --git a/include/mapnik/group/group_symbolizer_properties.hpp b/include/mapnik/group/group_symbolizer_properties.hpp new file mode 100644 index 000000000..04b5bd921 --- /dev/null +++ b/include/mapnik/group/group_symbolizer_properties.hpp @@ -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 +#include + +// stl +#include + +namespace mapnik +{ +struct group_rule; +typedef std::shared_ptr group_rule_ptr; +typedef std::vector 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_ptr; + +} //ns mapnik + +#endif // GROUP_PROPERTIES_HPP diff --git a/include/mapnik/symbolizer.hpp b/include/mapnik/symbolizer.hpp index 0a00b223a..023238f6d 100644 --- a/include/mapnik/symbolizer.hpp +++ b/include/mapnik/symbolizer.hpp @@ -37,6 +37,7 @@ #include #include #include +#include // stl #include #include @@ -78,7 +79,6 @@ struct enumeration_wrapper } }; - typedef std::vector > dash_array; struct MAPNIK_DECL symbolizer_base @@ -94,7 +94,8 @@ struct MAPNIK_DECL symbolizer_base transform_type, text_placements_ptr, dash_array, - raster_colorizer_ptr> value_type; + raster_colorizer_ptr, + group_symbolizer_properties_ptr> value_type; typedef mapnik::keys key_type; typedef std::map cont_type; cont_type properties; @@ -121,7 +122,9 @@ enum class property_types : std::uint8_t target_transform, target_placement, 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) @@ -415,6 +418,7 @@ struct MAPNIK_DECL polygon_pattern_symbolizer : public symbolizer_base {}; struct MAPNIK_DECL markers_symbolizer : public symbolizer_base {}; struct MAPNIK_DECL raster_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 {}; // symbolizer @@ -428,6 +432,7 @@ typedef boost::variant symbolizer; diff --git a/include/mapnik/symbolizer_keys.hpp b/include/mapnik/symbolizer_keys.hpp index 3951553f8..034aa343c 100644 --- a/include/mapnik/symbolizer_keys.hpp +++ b/include/mapnik/symbolizer_keys.hpp @@ -78,6 +78,10 @@ enum class keys : std::uint8_t point_placement_type, colorizer, halo_transform, + num_columns, + start_column, + repeat_key, + group_properties, MAX_SYMBOLIZER_KEY }; diff --git a/include/mapnik/text/text_properties.hpp b/include/mapnik/text/text_properties.hpp index 6cfa3073b..8e0fa9927 100644 --- a/include/mapnik/text/text_properties.hpp +++ b/include/mapnik/text/text_properties.hpp @@ -165,6 +165,8 @@ class text_layout; struct MAPNIK_DECL 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. */ 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!). */ diff --git a/src/agg/process_group_symbolizer.cpp b/src/agg/process_group_symbolizer.cpp new file mode 100644 index 000000000..25022ad32 --- /dev/null +++ b/src/agg/process_group_symbolizer.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// agg +#include "agg_trans_affine.h" + +// stl +#include + +// boost +#include + +namespace mapnik { + +template +void agg_renderer::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 columns; + attribute_collector column_collector(columns); + expression_attributes > rk_attr(columns); + + expression_ptr repeat_key = get(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(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(); + + // 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 > 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(sym, keys::start_column); + int end = start + get(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(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(*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::process(group_symbolizer const&, + mapnik::feature_impl &, + proj_transform const&); + +} diff --git a/src/build.py b/src/build.py index 3c3fc90fc..685a39b0f 100644 --- a/src/build.py +++ b/src/build.py @@ -233,6 +233,8 @@ source = Split( text/placements/dummy.cpp text/placements/list.cpp text/placements/simple.cpp + group/group_layout_manager.cpp + group/group_rule.cpp xml_tree.cpp config_error.cpp color_factory.cpp @@ -297,6 +299,7 @@ source += Split( agg/process_raster_symbolizer.cpp agg/process_shield_symbolizer.cpp agg/process_markers_symbolizer.cpp + agg/process_group_symbolizer.cpp agg/process_debug_symbolizer.cpp """ ) diff --git a/src/group/group_layout_manager.cpp b/src/group/group_layout_manager.cpp new file mode 100644 index 000000000..86853aeee --- /dev/null +++ b/src/group/group_layout_manager.cpp @@ -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 + +// boost +#include + +// std +#include + +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 &member_boxes_; + + // The vector to populate with item offsets + vector &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 &member_bboxes, + vector &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 + // 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(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; + } +} + +} diff --git a/src/group/group_rule.cpp b/src/group/group_rule.cpp new file mode 100644 index 000000000..3f0e1babd --- /dev/null +++ b/src/group/group_rule.cpp @@ -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 +#include + +// stl +#include + +namespace mapnik +{ + +group_rule::group_rule(const expression_ptr& filter = std::make_shared(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); +} + +} + diff --git a/src/load_map.cpp b/src/load_map.cpp index 31f823cb5..1c6893f3c 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -54,6 +54,7 @@ #include #include #include +#include // boost @@ -101,6 +102,7 @@ private: 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_line_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_raster_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_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); void parse_stroke(symbolizer_base & symbol, xml_node const & sym); void ensure_font_face(std::string const& face_name); @@ -792,58 +800,7 @@ void map_parser::parse_rule(feature_type_style & style, xml_node const& node) rule.set_max_scale(child->get_value()); } - for (auto const& sym_node : node) - { - switch (name2int(sym_node.name().c_str())) - { - case name2int("PointSymbolizer"): - parse_point_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("LinePatternSymbolizer"): - parse_line_pattern_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("PolygonPatternSymbolizer"): - parse_polygon_pattern_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("TextSymbolizer"): - parse_text_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("ShieldSymbolizer"): - parse_shield_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("LineSymbolizer"): - parse_line_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("PolygonSymbolizer"): - parse_polygon_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("BuildingSymbolizer"): - parse_building_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("RasterSymbolizer"): - parse_raster_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("MarkersSymbolizer"): - parse_markers_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - case name2int("DebugSymbolizer"): - parse_debug_symbolizer(rule, sym_node); - sym_node.set_processed(true); - break; - default: - break; - } - } + parse_symbolizers(rule, node); style.add_rule(rule); } @@ -857,6 +814,66 @@ void map_parser::parse_rule(feature_type_style & style, xml_node const& node) } } +void map_parser::parse_symbolizers(rule & rule, xml_node const & node) +{ + for (auto const& sym_node : node) + { + switch (name2int(sym_node.name().c_str())) + { + case name2int("PointSymbolizer"): + parse_point_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("LinePatternSymbolizer"): + parse_line_pattern_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("PolygonPatternSymbolizer"): + parse_polygon_pattern_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("TextSymbolizer"): + parse_text_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("ShieldSymbolizer"): + parse_shield_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("LineSymbolizer"): + parse_line_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("PolygonSymbolizer"): + parse_polygon_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("BuildingSymbolizer"): + parse_building_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("RasterSymbolizer"): + parse_raster_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("MarkersSymbolizer"): + parse_markers_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("GroupSymbolizer"): + parse_group_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + case name2int("DebugSymbolizer"): + parse_debug_symbolizer(rule, sym_node); + sym_node.set_processed(true); + break; + default: + break; + } + } +} + // helper method template void set_symbolizer_property(Symbolizer & sym, keys key, xml_node const & node) @@ -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(); + + set_symbolizer_property(symbol, keys::num_columns, sym); + set_symbolizer_property(symbol, keys::start_column, sym); + set_symbolizer_property(symbol, keys::repeat_key, sym); + + text_placements_ptr placements = std::make_shared(); + placements->defaults.placement_properties_from_xml(sym); + put(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) { debug_symbolizer symbol; @@ -1650,6 +1719,71 @@ bool map_parser::parse_raster_colorizer(raster_colorizer_ptr const& rc, 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(); + } + else + { + filter = std::make_shared(true); + } + + if (rptkey_child) + { + repeat_key = rptkey_child->get_value(); + } + + group_rule_ptr rule = std::make_shared(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 item_margin = node.get_opt_attr("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 item_margin = node.get_opt_attr("item-margin"); + if (item_margin) layout.set_item_margin(*item_margin); + + optional max_difference = node.get_opt_attr("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) { if (! font_manager_.get_face(face_name)) diff --git a/src/symbolizer_keys.cpp b/src/symbolizer_keys.cpp index 93dda6408..b3be1f90e 100644 --- a/src/symbolizer_keys.cpp +++ b/src/symbolizer_keys.cpp @@ -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(e.value)).as_string();}, property_types::target_double }, 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{ "num-columns", static_cast(0), nullptr, property_types::target_integer}, + property_meta_type{ "start-column", static_cast(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) diff --git a/src/text/text_properties.cpp b/src/text/text_properties.cpp index 34456446c..ef4ba945f 100644 --- a/src/text/text_properties.cpp +++ b/src/text/text_properties.cpp @@ -138,7 +138,7 @@ formatting::node_ptr text_symbolizer_properties::format_tree() const 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 placement_ = sym.get_opt_attr("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_; optional largest_bbox_only_ = sym.get_opt_attr("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 max_char_angle_delta_ = sym.get_opt_attr("max-char-angle-delta"); if (max_char_angle_delta_) max_char_angle_delta=(*max_char_angle_delta_)*(M_PI/180); optional upright_ = sym.get_opt_attr("upright");