support expressions in text-placements - closes #2597

This commit is contained in:
Dane Springmeyer 2014-12-19 19:05:54 -08:00
parent 008168f75c
commit dc8253ec2a
10 changed files with 205 additions and 96 deletions

View File

@ -33,6 +33,9 @@ namespace mapnik
using dimension_type = std::pair<double,double>;
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.

View File

@ -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;
};

View File

@ -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);

View File

@ -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<directions_e> && direction,
std::vector<int> && 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<directions_e> direction_;
std::vector<int> 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<directions_e> direction_;
mutable std::vector<int> text_sizes_;
text_placements_simple const* parent_;
};

View File

@ -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<text_placement_info_dummy>(this, scale_factor);
}

View File

@ -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<text_placement_info_list>(this, scale_factor);
}

View File

@ -25,6 +25,10 @@
#include <mapnik/text/placements/simple.hpp>
#include <mapnik/ptree_helpers.hpp>
#include <mapnik/xml_node.hpp>
#include <mapnik/value.hpp>
#include <mapnik/expression_evaluator.hpp>
#include <mapnik/symbolizer.hpp>
#include <mapnik/expression_string.hpp>
// 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<char, directions_e>
{
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<directions_e> & direction,
std::vector<int> & 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<text_placement_info_simple>(this, scale_factor);
std::string evaluated_positions = util::apply_visitor(extract_value<std::string>(feature,vars), positions_);
return std::make_shared<text_placement_info_simple>(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<char, directions_e>
text_placements_simple::text_placements_simple(symbolizer_base::value_type const& positions,
std::vector<directions_e> && direction,
std::vector<int> && text_sizes)
: direction_(direction),
text_sizes_(text_sizes),
positions_(positions) { }
namespace detail {
struct serialize_positions : public util::static_visitor<std::string>
{
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 <typename T>
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<text_placements_simple>(
xml.get_attr<std::string>("placements", "X"));
ptr->defaults.from_xml(xml, fontsets, is_shield);
return ptr;
// TODO - handle X cleaner
std::string placements_string = xml.get_attr<std::string>("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<text_placements_simple>(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<expression_ptr> val = xml.get_opt_attr<expression_ptr>("placements");
if (val)
{
text_placements_ptr ptr = std::make_shared<text_placements_simple>(*val);
ptr->defaults.from_xml(xml, fontsets, is_shield);
return ptr;
}
}
catch (std::exception const& ex)
{
// otherwise ensure it is valid
std::vector<directions_e> direction;
std::vector<int> 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<text_placements_simple>(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<text_placements_simple>(placements_string);
ptr->defaults.from_xml(xml, fontsets, is_shield);
return ptr;
}
}
return text_placements_ptr();
}
} //ns mapnik

View File

@ -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<text_placements_ptr>(sym_, keys::text_placements_)->get_placement_info(scale_factor)),
info_ptr_(mapnik::get<text_placements_ptr>(sym_, keys::text_placements_)->get_placement_info(scale_factor,feature_,vars_)),
text_props_(evaluate_text_properties(info_ptr_->properties,feature_,vars_))
{
initialize_geometries();

View File

@ -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
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"
1 lat long name nr color placements
2 0 0 Test one 1 #ff0000 N,S,E,W,SW,10,5
3 0 0.1 Test two 2 red N,S,E,W,SW,10,5
4 0 0.2 Test three 3 #00ff00 N,S,E,W,SW,10,5
5 0 0.3 Test four 4 green N,S,E,W,SW,10,5
6 0 0.4 Test five 5 #0000ff N,S,E,W,SW,10,5
7 0 0.5 Test six 6 blue N,S,E,W,SW,10,5
8 0 0.6 Test seven 7 #000000 N,S,E,W,SW,10,5
9 0 0.7 Test eight 8 black N,S,E,W,SW,10,5
10 0 0.8 Test nine 9 #ffffff N,S,E,W,SW,10,5
11 0 0.9 Test ten 10 white N,S,E,W,SW,10,5

View File

@ -17,7 +17,7 @@
<Style name="My Style">
<Rule>
<PointSymbolizer clip="false" />
<TextSymbolizer clip="false" face-name="DejaVu Sans Book" size="16" placement="point" dx="5" dy="16" placement-type="simple" placements="N,S,E,W,SW,10,5">[name]</TextSymbolizer>
<TextSymbolizer clip="false" face-name="DejaVu Sans Book" size="16" placement="point" dx="5" dy="16" placement-type="simple" placements="[placements]">[name]</TextSymbolizer>
</Rule>
</Style>