From dc8253ec2a64dbfdc54fefae40fb002d04f9987c Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 19 Dec 2014 19:05:54 -0800 Subject: [PATCH] support expressions in text-placements - closes #2597 --- include/mapnik/text/placements/base.hpp | 5 +- include/mapnik/text/placements/dummy.hpp | 4 +- include/mapnik/text/placements/list.hpp | 5 +- include/mapnik/text/placements/simple.hpp | 27 +-- src/text/placements/dummy.cpp | 2 +- src/text/placements/list.cpp | 2 +- src/text/placements/simple.cpp | 230 +++++++++++++++------- src/text/symbolizer_helpers.cpp | 2 +- tests/visual_tests/data/points.csv | 22 +-- tests/visual_tests/styles/simple.xml | 2 +- 10 files changed, 205 insertions(+), 96 deletions(-) diff --git a/include/mapnik/text/placements/base.hpp b/include/mapnik/text/placements/base.hpp index 007539e7a..631386d4b 100644 --- a/include/mapnik/text/placements/base.hpp +++ b/include/mapnik/text/placements/base.hpp @@ -33,6 +33,9 @@ namespace mapnik using dimension_type = std::pair; class MAPNIK_DECL text_placements; +class feature_impl; +struct attribute; + // Generate a possible placement. // This placement has first to be tested by placement_finder to verify it // can actually be used. @@ -84,7 +87,7 @@ public: // return text_placement_info_ptr(new text_placement_info_XXX(this)); // } - virtual text_placement_info_ptr get_placement_info(double scale_factor) const = 0; + virtual text_placement_info_ptr get_placement_info(double scale_factor, feature_impl const& feature, attributes const& vars) const = 0; // Get a list of all expressions used in any placement. // This function is used to collect attributes. diff --git a/include/mapnik/text/placements/dummy.hpp b/include/mapnik/text/placements/dummy.hpp index d17ee8171..18d81fea6 100644 --- a/include/mapnik/text/placements/dummy.hpp +++ b/include/mapnik/text/placements/dummy.hpp @@ -30,12 +30,14 @@ namespace mapnik { class text_placement_info_dummy; +class feature_impl; +struct attribute; // Dummy placement algorithm. Always takes the default value. class MAPNIK_DECL text_placements_dummy: public text_placements { public: - text_placement_info_ptr get_placement_info(double scale_factor) const; + text_placement_info_ptr get_placement_info(double scale_factor, feature_impl const& feature, attributes const& vars) const; friend class text_placement_info_dummy; }; diff --git a/include/mapnik/text/placements/list.hpp b/include/mapnik/text/placements/list.hpp index d976d0090..1fa4c8712 100644 --- a/include/mapnik/text/placements/list.hpp +++ b/include/mapnik/text/placements/list.hpp @@ -26,14 +26,15 @@ namespace mapnik { class text_placement_info_list; - +class feature_impl; +struct attribute; // Tries a list of placements. class text_placements_list: public text_placements { public: text_placements_list(); - text_placement_info_ptr get_placement_info(double scale_factor) const; + text_placement_info_ptr get_placement_info(double scale_factor, feature_impl const& feature, attributes const& vars) const; virtual void add_expressions(expression_set & output) const; text_symbolizer_properties & add(); text_symbolizer_properties & get(unsigned i); diff --git a/include/mapnik/text/placements/simple.hpp b/include/mapnik/text/placements/simple.hpp index 15cb1b01a..2a9b983d7 100644 --- a/include/mapnik/text/placements/simple.hpp +++ b/include/mapnik/text/placements/simple.hpp @@ -29,21 +29,25 @@ namespace mapnik { class text_placement_info_simple; +class feature_impl; +struct attribute; // Automatically generates placement options from a user selected list of directions and text sizes. class text_placements_simple: public text_placements { public: - text_placements_simple(); - text_placements_simple(std::string const& positions); - text_placement_info_ptr get_placement_info(double scale_factor) const; - void set_positions(std::string const& positions); - std::string get_positions(); - static text_placements_ptr from_xml(xml_node const &xml, fontset_map const & fontsets, bool is_shield); -private: - std::string positions_; + text_placements_simple(symbolizer_base::value_type const& positions); + text_placements_simple(symbolizer_base::value_type const& positions, + std::vector && direction, + std::vector && text_sizes); + text_placement_info_ptr get_placement_info(double scale_factor, feature_impl const& feature, attributes const& vars) const; + std::string get_positions() const; + static text_placements_ptr from_xml(xml_node const& xml, fontset_map const& fontsets, bool is_shield); + void init_positions(std::string const& positions) const; std::vector direction_; std::vector text_sizes_; +private: + symbolizer_base::value_type positions_; friend class text_placement_info_simple; }; @@ -53,14 +57,15 @@ class text_placement_info_simple : public text_placement_info { public: text_placement_info_simple(text_placements_simple const* parent, - double scale_factor) - : text_placement_info(parent, scale_factor), - state(0), position_state(0), parent_(parent) {} + std::string const& evaluated_positions, + double scale_factor); bool next() const; protected: bool next_position_only() const; mutable unsigned state; mutable unsigned position_state; + mutable std::vector direction_; + mutable std::vector text_sizes_; text_placements_simple const* parent_; }; diff --git a/src/text/placements/dummy.cpp b/src/text/placements/dummy.cpp index e172af7d3..569b5df48 100644 --- a/src/text/placements/dummy.cpp +++ b/src/text/placements/dummy.cpp @@ -33,7 +33,7 @@ bool text_placement_info_dummy::next() const } text_placement_info_ptr text_placements_dummy::get_placement_info( - double scale_factor) const + double scale_factor, feature_impl const& feature, attributes const& vars) const { return std::make_shared(this, scale_factor); } diff --git a/src/text/placements/list.cpp b/src/text/placements/list.cpp index 720ba3b96..1c40b68cf 100644 --- a/src/text/placements/list.cpp +++ b/src/text/placements/list.cpp @@ -65,7 +65,7 @@ text_symbolizer_properties & text_placements_list::get(unsigned i) } -text_placement_info_ptr text_placements_list::get_placement_info(double scale_factor) const +text_placement_info_ptr text_placements_list::get_placement_info(double scale_factor, feature_impl const& feature, attributes const& vars) const { return std::make_shared(this, scale_factor); } diff --git a/src/text/placements/simple.cpp b/src/text/placements/simple.cpp index d0bfbe253..c365ae403 100644 --- a/src/text/placements/simple.cpp +++ b/src/text/placements/simple.cpp @@ -25,6 +25,10 @@ #include #include #include +#include +#include +#include +#include // boost #pragma GCC diagnostic push @@ -47,14 +51,86 @@ namespace phoenix = boost::phoenix; using phoenix::push_back; using phoenix::ref; +struct direction_name : qi::symbols +{ + direction_name() + { + add + ("N" , NORTH) + ("E" , EAST) + ("S" , SOUTH) + ("W" , WEST) + ("NE", NORTHEAST) + ("SE", SOUTHEAST) + ("NW", NORTHWEST) + ("SW", SOUTHWEST) + ("X" , EXACT_POSITION) + ; + } + +}; + +// Position string: [POS][SIZE] +// [POS] is any combination of +// N, E, S, W, NE, SE, NW, SW, X (exact position) (separated by commas) +// [SIZE] is a list of font sizes, separated by commas. The first font size +// is always the one given in the TextSymbolizer's parameters. +// First all directions are tried, then font size is reduced +// and all directions are tried again. The process ends when a placement is +// found or the last fontsize is tried without success. +// Example: N,S,15,10,8 (tries placement above, then below and if +// that fails it tries the additional font sizes 15, 10 and 8. + +bool parse_positions(std::string const& evaluated_positions, + std::vector & direction, + std::vector & text_sizes) +{ + direction_name names; + boost::spirit::ascii::space_type space; + qi::_1_type _1; + qi::float_type float_; + std::string::const_iterator first = evaluated_positions.begin(); + std::string::const_iterator last = evaluated_positions.end(); + bool r = qi::phrase_parse(first, last, + (names[push_back(phoenix::ref(direction), _1)] % ',') + >> *(',' >> float_[push_back(phoenix::ref(text_sizes), _1)]), + space); + if (first != last) + { + return false; + } + return r; +} + + +text_placement_info_simple::text_placement_info_simple(text_placements_simple const* parent, + std::string const& evaluated_positions, + double scale_factor) +: text_placement_info(parent, scale_factor), + state(0), + position_state(0), + direction_(parent->direction_), + text_sizes_(parent->text_sizes_), + parent_(parent) +{ + if (direction_.empty() && !parse_positions(evaluated_positions,direction_,text_sizes_)) + { + MAPNIK_LOG_ERROR(text_placements) << "Could not parse text_placement_simple placement string ('" << evaluated_positions << "')"; + if (direction_.size() == 0) + { + MAPNIK_LOG_ERROR(text_placements) << "text_placements_simple with no valid placements! ('"<< evaluated_positions <<"')"; + } + } +} + bool text_placement_info_simple::next() const { while (true) { if (state > 0) { - if (state > parent_->text_sizes_.size()) return false; - properties.format_defaults.text_size = value_double(parent_->text_sizes_[state-1]); + if (state > text_sizes_.size()) return false; + properties.format_defaults.text_size = value_double(text_sizes_[state-1]); } if (!next_position_only()) { @@ -68,90 +144,112 @@ bool text_placement_info_simple::next() const bool text_placement_info_simple::next_position_only() const { - if (position_state >= parent_->direction_.size()) return false; - //directions_e dir = parent_->direction_[position_state]; - properties.layout_defaults.dir = parent_->direction_[position_state]; + if (position_state >= direction_.size()) return false; + properties.layout_defaults.dir = direction_[position_state]; ++position_state; return true; } -text_placement_info_ptr text_placements_simple::get_placement_info(double scale_factor) const +text_placement_info_ptr text_placements_simple::get_placement_info(double scale_factor, feature_impl const& feature, attributes const& vars) const { - return std::make_shared(this, scale_factor); + std::string evaluated_positions = util::apply_visitor(extract_value(feature,vars), positions_); + return std::make_shared(this, evaluated_positions, scale_factor); } -// Position string: [POS][SIZE] -// [POS] is any combination of -// N, E, S, W, NE, SE, NW, SW, X (exact position) (separated by commas) -// [SIZE] is a list of font sizes, separated by commas. The first font size -// is always the one given in the TextSymbolizer's parameters. -// First all directions are tried, then font size is reduced -// and all directions are tried again. The process ends when a placement is -// found or the last fontsize is tried without success. -// Example: N,S,15,10,8 (tries placement above, then below and if -// that fails it tries the additional font sizes 15, 10 and 8. +text_placements_simple::text_placements_simple(symbolizer_base::value_type const& positions) + : direction_(), + text_sizes_(), + positions_(positions) { } -void text_placements_simple::set_positions(std::string const& positions) -{ - positions_ = positions; - struct direction_name_ : qi::symbols +text_placements_simple::text_placements_simple(symbolizer_base::value_type const& positions, + std::vector && direction, + std::vector && text_sizes) + : direction_(direction), + text_sizes_(text_sizes), + positions_(positions) { } + +namespace detail { + struct serialize_positions : public util::static_visitor { - direction_name_() + serialize_positions() {} + + std::string operator() (expression_ptr const& expr) const { - add - ("N" , NORTH) - ("E" , EAST) - ("S" , SOUTH) - ("W" , WEST) - ("NE", NORTHEAST) - ("SE", SOUTHEAST) - ("NW", NORTHWEST) - ("SW", SOUTHWEST) - ("X" , EXACT_POSITION) - ; + if (expr) return to_expression_string(*expr); + return ""; } - } direction_name; - boost::spirit::ascii::space_type space; - qi::_1_type _1; - qi::float_type float_; + std::string operator() (std::string const val) const + { + return val; + } - std::string::const_iterator first = positions.begin(), last = positions.end(); - qi::phrase_parse(first, last, - (direction_name[push_back(phoenix::ref(direction_), _1)] % ',') - >> *(',' >> float_[push_back(phoenix::ref(text_sizes_), _1)]), - space); - if (first != last) - { - MAPNIK_LOG_WARN(text_placements) << "Could not parse text_placement_simple placement string ('" << positions << "')"; - } - if (direction_.size() == 0) - { - MAPNIK_LOG_WARN(text_placements) << "text_placements_simple with no valid placements! ('"<< positions<<"')"; - } + template + std::string operator() (T const& val) const + { + return ""; + } + }; } -text_placements_simple::text_placements_simple() +std::string text_placements_simple::get_positions() const { - set_positions("X"); -} - -text_placements_simple::text_placements_simple(std::string const& positions) -{ - set_positions(positions); -} - -std::string text_placements_simple::get_positions() -{ - return positions_; //TODO: Build string from data in direction_ and text_sizes_ + return util::apply_visitor(detail::serialize_positions(), positions_); } text_placements_ptr text_placements_simple::from_xml(xml_node const& xml, fontset_map const& fontsets, bool is_shield) { - text_placements_ptr ptr = std::make_shared( - xml.get_attr("placements", "X")); - ptr->defaults.from_xml(xml, fontsets, is_shield); - return ptr; + // TODO - handle X cleaner + std::string placements_string = xml.get_attr("placements", "X"); + // like set_property_from_xml in properties_util.hpp + if (!placements_string.empty()) + { + if (placements_string == "X") + { + text_placements_ptr ptr = std::make_shared(placements_string); + ptr->defaults.from_xml(xml, fontsets, is_shield); + return ptr; + } + else + { + try + { + // we don't use parse_expression(placements_string) directly here to benefit from the cache in the xml_node + boost::optional val = xml.get_opt_attr("placements"); + if (val) + { + text_placements_ptr ptr = std::make_shared(*val); + ptr->defaults.from_xml(xml, fontsets, is_shield); + return ptr; + } + } + catch (std::exception const& ex) + { + // otherwise ensure it is valid + std::vector direction; + std::vector text_sizes; + if (!parse_positions(placements_string,direction,text_sizes)) + { + MAPNIK_LOG_ERROR(text_placements) << "Could not parse text_placement_simple placement string ('" << placements_string << "')"; + if (direction.size() == 0) + { + MAPNIK_LOG_ERROR(text_placements) << "text_placements_simple with no valid placements! ('"<< placements_string <<"')"; + } + return text_placements_ptr(); + } + else + { + text_placements_ptr ptr = std::make_shared(placements_string,std::move(direction),std::move(text_sizes)); + ptr->defaults.from_xml(xml, fontsets, is_shield); + return ptr; + } + } + text_placements_ptr ptr = std::make_shared(placements_string); + ptr->defaults.from_xml(xml, fontsets, is_shield); + return ptr; + } + } + return text_placements_ptr(); } } //ns mapnik diff --git a/src/text/symbolizer_helpers.cpp b/src/text/symbolizer_helpers.cpp index 0c8074509..36b6478a7 100644 --- a/src/text/symbolizer_helpers.cpp +++ b/src/text/symbolizer_helpers.cpp @@ -58,7 +58,7 @@ base_symbolizer_helper::base_symbolizer_helper( dims_(0, 0, width, height), query_extent_(query_extent), scale_factor_(scale_factor), - info_ptr_(mapnik::get(sym_, keys::text_placements_)->get_placement_info(scale_factor)), + info_ptr_(mapnik::get(sym_, keys::text_placements_)->get_placement_info(scale_factor,feature_,vars_)), text_props_(evaluate_text_properties(info_ptr_->properties,feature_,vars_)) { initialize_geometries(); diff --git a/tests/visual_tests/data/points.csv b/tests/visual_tests/data/points.csv index 16b469d2f..3cd780b40 100644 --- a/tests/visual_tests/data/points.csv +++ b/tests/visual_tests/data/points.csv @@ -1,11 +1,11 @@ -lat,long,name,nr,color -0,0,Test one,1,#ff0000 -0,0.1,Test two,2,red -0,0.2,Test three,3,#00ff00 -0,0.3,Test four,4,green -0,0.4,Test five,5,#0000ff -0,0.5,Test six,6,blue -0,0.6,Test seven,7,#000000 -0,0.7,Test eight,8,black -0,0.8,Test nine,9,#ffffff -0,0.9,Test ten,10,white \ No newline at end of file +lat,long,name,nr,color,placements +0,0,Test one,1,#ff0000,"N,S,E,W,SW,10,5" +0,0.1,Test two,2,red,"N,S,E,W,SW,10,5" +0,0.2,Test three,3,#00ff00,"N,S,E,W,SW,10,5" +0,0.3,Test four,4,green,"N,S,E,W,SW,10,5" +0,0.4,Test five,5,#0000ff,"N,S,E,W,SW,10,5" +0,0.5,Test six,6,blue,"N,S,E,W,SW,10,5" +0,0.6,Test seven,7,#000000,"N,S,E,W,SW,10,5" +0,0.7,Test eight,8,black,"N,S,E,W,SW,10,5" +0,0.8,Test nine,9,#ffffff,"N,S,E,W,SW,10,5" +0,0.9,Test ten,10,white,"N,S,E,W,SW,10,5" \ No newline at end of file diff --git a/tests/visual_tests/styles/simple.xml b/tests/visual_tests/styles/simple.xml index ff4492542..7105f7cc8 100644 --- a/tests/visual_tests/styles/simple.xml +++ b/tests/visual_tests/styles/simple.xml @@ -17,7 +17,7 @@