mirror of
https://github.com/mapnik/mapnik.git
synced 2025-12-08 20:13:09 +00:00
support expressions in text-placements - closes #2597
This commit is contained in:
parent
008168f75c
commit
dc8253ec2a
@ -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.
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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"
|
||||
|
@ -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>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user