1. patch from Toby that allows repeated labels to be placed on a line

geometry (such as roads)
2. restored text labels for point geometry.
3. process 'spacing' attribute in load_map.cpp
This commit is contained in:
Artem Pavlenko 2006-11-01 23:17:05 +00:00
parent 51906ed2f8
commit e924b597f4
10 changed files with 228 additions and 188 deletions

View file

@ -49,6 +49,9 @@ void export_text_symbolizer()
.add_property("text_ratio",
&text_symbolizer::get_text_ratio,
&text_symbolizer::set_text_ratio)
.add_property("label_spacing",
&text_symbolizer::get_label_spacing,
&text_symbolizer::set_label_spacing)
.add_property("halo_radius",
&text_symbolizer::get_halo_radius,
&text_symbolizer::set_halo_radius)

View file

@ -280,11 +280,13 @@ popplaces_rule = Rule()
# The first parameter is the name of the attribute to use as the source of the
# text to label with. Then there is font size in points (I think?), and colour.
popplaces_text_symbolizer = TextSymbolizer('GEONAME', 10, Color('black'))
popplaces_text_symbolizer = TextSymbolizer('GEONAME',
'Bitstream Vera Sans Roman',
10, Color('black'))
# We set a "halo" around the text, which looks like an outline if thin enough,
# or an outright background if large enough.
popplaces_text_symbolizer.set_label_placement=label_placement.POINT_PLACEMENT
popplaces_text_symbolizer.halo_fill = Color('white')
popplaces_text_symbolizer.halo_radius = 1
popplaces_rule.symbols.append(popplaces_text_symbolizer)

View file

@ -36,6 +36,15 @@
namespace mapnik
{
struct placement_element
{
double starting_x;
double starting_y;
text_path path;
};
struct placement
{
typedef coord_transform2<CoordTransform,geometry_type> path_type;
@ -48,7 +57,8 @@ namespace mapnik
~placement();
string_info *info;
unsigned path_size() const;
string_info *info;
CoordTransform *ctrans;
const proj_transform *proj_trans;
@ -63,11 +73,10 @@ namespace mapnik
std::queue< Envelope<double> > envelopes;
//output
double starting_x;
double starting_y;
text_path path;
std::vector<placement_element> placements;
// caching output
placement_element current_placement;
//helpers
std::pair<double, double> get_position_at_distance(double target_distance);
@ -78,14 +87,18 @@ namespace mapnik
int wrap_width;
int text_ratio;
int label_spacing; // distance between repeated labels on a single geometry
};
class placement_finder : boost::noncopyable
{
public:
placement_finder(Envelope<double> e);
bool find_placement(placement *placement);
bool find_placements(placement *p);
protected:
bool find_placement_follow(placement *p);

View file

@ -93,9 +93,9 @@ namespace mapnik
}
};
struct text_path : private boost::noncopyable
struct text_path
{
struct character_node : boost::noncopyable
struct character_node
{
int c;
double x, y, angle;
@ -112,7 +112,7 @@ namespace mapnik
}
};
typedef boost::ptr_vector<character_node> character_nodes_t;
typedef std::vector<character_node> character_nodes_t;
character_nodes_t nodes_;
int itr_;
@ -120,11 +120,17 @@ namespace mapnik
std::pair<unsigned,unsigned> string_dimensions;
text_path() : itr_(0) {}
text_path(const text_path & other) : itr_(0)
{
nodes_ = other.nodes_;
string_dimensions = other.string_dimensions;
}
~text_path() {}
void add_node(int c, double x, double y, double angle)
{
nodes_.push_back(new character_node(c, x, y, angle));
nodes_.push_back(character_node(c, x, y, angle));
}
void vertex(int *c, double *x, double *y, double *angle)

View file

@ -45,8 +45,10 @@ namespace mapnik
std::string const& get_name() const;
unsigned get_text_ratio() const; // target ratio for text bounding box in pixels
void set_text_ratio(unsigned ratio);
unsigned get_wrap_width() const; // target ratio for text bounding box in pixels
unsigned get_wrap_width() const; // width to wrap text at, or trigger ratio
void set_wrap_width(unsigned ratio);
unsigned get_label_spacing() const; // spacing between repeated labels on lines
void set_label_spacing(unsigned spacing);
unsigned get_text_size() const;
std::string const& get_face_name() const;
Color const& get_fill() const;
@ -66,6 +68,7 @@ namespace mapnik
unsigned size_;
unsigned text_ratio_;
unsigned wrap_width_;
unsigned label_spacing_;
Color fill_;
Color halo_fill_;
unsigned halo_radius_;

View file

@ -148,7 +148,7 @@ postgis_datasource::postgis_datasource(const parameters& params)
desc_.add_descriptor(attribute_descriptor(fld_name,mapnik::Double,false,length));
case 1042: // bpchar
case 1043: // varchar
case 25: // text
case 25: // text
desc_.add_descriptor(attribute_descriptor(fld_name,mapnik::String));
break;
default: // shouldn't get here

View file

@ -346,24 +346,26 @@ namespace mapnik
placement text_placement(&info, &t_, &prj_trans, geom, std::pair<double, double>(w, h) );
bool found = finder_.find_placement(&text_placement);
bool found = finder_.find_placements(&text_placement);
if (!found) {
return;
}
double x = text_placement.starting_x;
double y = text_placement.starting_y;
int px=int(floor(x - 0.5 * w));
int py=int(floor(y - 0.5 * h));
pixmap_.set_rectangle_alpha(px,py,*data);
Envelope<double> dim = ren.prepare_glyphs(&text_placement.path);
//If has_placement
ren.render(x,y);
for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii)
{
double x = text_placement.placements[ii].starting_x;
double y = text_placement.placements[ii].starting_y;
int px=int(floor(x - 0.5 * w));
int py=int(floor(y - 0.5 * h));
pixmap_.set_rectangle_alpha(px,py,*data);
Envelope<double> dim = ren.prepare_glyphs(&text_placement.placements[ii].path);
ren.render(x,y);
}
}
}
}
@ -496,33 +498,37 @@ namespace mapnik
ren.set_fill(fill);
ren.set_halo_fill(sym.get_halo_fill());
ren.set_halo_radius(sym.get_halo_radius());
string_info info;
ren.get_string_info(text, &info);
placement text_placement(&info, &t_, &prj_trans, geom, sym.get_label_placement());
text_placement.text_ratio = sym.get_text_ratio();
text_placement.wrap_width = sym.get_wrap_width();
bool found = finder_.find_placement(&text_placement);
text_placement.label_spacing = sym.get_label_spacing();
bool found = finder_.find_placements(&text_placement);
if (!found) {
return;
}
double x = text_placement.starting_x;
double y = text_placement.starting_y;
Envelope<double> dim = ren.prepare_glyphs(&text_placement.path);
Envelope<double> text_box(x + dim.minx() ,y - dim.maxy(), x + dim.maxx(),y - dim.miny());
if (sym.get_halo_radius() > 0)
for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii)
{
text_box.width(text_box.width() + sym.get_halo_radius()*2);
text_box.height(text_box.height() + sym.get_halo_radius()*2);
double x = text_placement.placements[ii].starting_x;
double y = text_placement.placements[ii].starting_y;
Envelope<double> dim = ren.prepare_glyphs(&text_placement.placements[ii].path);
Envelope<double> text_box(x + dim.minx() ,y - dim.maxy(), x + dim.maxx(),y - dim.miny());
if (sym.get_halo_radius() > 0)
{
text_box.width(text_box.width() + sym.get_halo_radius()*2);
text_box.height(text_box.height() + sym.get_halo_radius()*2);
}
ren.render(x,y);
}
ren.render(x,y);
}
}
}

View file

@ -126,7 +126,16 @@ namespace mapnik
if ( sym.first == "PointSymbolizer")
{
std::cout << sym.first << "\n";
std::string file =
sym.second.get<std::string>("<xmlattr>.file");
std::string type =
sym.second.get<std::string>("<xmlattr>.type");
unsigned width =
sym.second.get<unsigned>("<xmlattr>.width");
unsigned height =
sym.second.get<unsigned>("<xmlattr>.height");
rule.append(point_symbolizer(file,type,width,height));
}
else if ( sym.first == "LinePatternSymbolizer")
{
@ -163,6 +172,7 @@ namespace mapnik
{
text_symbol.set_label_placement(line_placement);
}
// halo fill and radius
boost::optional<std::string> halo_fill =
sym.second.get_optional<std::string>("<xmlattr>.halo_fill");
@ -195,6 +205,13 @@ namespace mapnik
text_symbol.set_wrap_width(*wrap_width);
}
// spacing between repeated labels on lines
boost::optional<unsigned> spacing =
sym.second.get_optional<unsigned>("<xmlattr>.spacing");
if (spacing)
{
text_symbol.set_label_spacing(*spacing);
}
rule.append(text_symbol);
}
else if ( sym.first == "ShieldSymbolizer")

View file

@ -41,26 +41,31 @@
namespace mapnik
{
placement::placement(string_info *info_, CoordTransform *ctrans_, const proj_transform *proj_trans_, geometry_ptr geom_, std::pair<double, double> dimensions_)
: info(info_), ctrans(ctrans_), proj_trans(proj_trans_), geom(geom_), label_placement(point_placement), dimensions(dimensions_), has_dimensions(true), shape_path(*ctrans_, *geom_, *proj_trans_), total_distance_(-1.0), wrap_width(0), text_ratio(0)
: info(info_), ctrans(ctrans_), proj_trans(proj_trans_), geom(geom_), label_placement(point_placement), dimensions(dimensions_), has_dimensions(true), shape_path(*ctrans_, *geom_, *proj_trans_), total_distance_(-1.0), wrap_width(0), text_ratio(0), label_spacing(0)
{
}
//For text
placement::placement(string_info *info_, CoordTransform *ctrans_, const proj_transform *proj_trans_, geometry_ptr geom_, label_placement_e placement_)
: info(info_), ctrans(ctrans_), proj_trans(proj_trans_), geom(geom_), label_placement(placement_), has_dimensions(false), shape_path(*ctrans_, *geom_, *proj_trans_), total_distance_(-1.0), wrap_width(0), text_ratio(0)
: info(info_), ctrans(ctrans_), proj_trans(proj_trans_), geom(geom_), label_placement(placement_), has_dimensions(false), shape_path(*ctrans_, *geom_, *proj_trans_), total_distance_(-1.0), wrap_width(0), text_ratio(0), label_spacing(0)
{
}
placement::~placement()
{
}
unsigned placement::path_size() const
{
return geom->num_points();
}
std::pair<double, double> placement::get_position_at_distance(double target_distance)
{
double old_x, old_y, new_x, new_y;
double x, y;
x = y = 0.0;
double distance = 0.0;
shape_path.rewind(0);
@ -132,11 +137,12 @@ namespace mapnik
placement_finder::placement_finder(Envelope<double> e)
: detector_(e)
: detector_(e)
{
}
bool placement_finder::find_placement(placement *p)
bool placement_finder::find_placements(placement *p)
{
if (p->label_placement == point_placement)
{
@ -156,37 +162,76 @@ namespace mapnik
double string_width = string_dimensions.first;
// double string_height = string_dimensions.second;
std::clog << "trying to place string: ";
for (unsigned int ii = 0; ii < p->info->num_characters(); ++ii)
std::clog << static_cast<char> (p->info->at(ii).character);
std::clog << std::endl;
double distance = p->get_total_distance();
//~ double delta = string_width/distance;
double delta = distance/100.0;
for (double i = 0; i < (distance - string_width)/2.0; i += delta)
if (string_width > distance)
{
p->clear_envelopes();
if ( build_path_follow(p, (distance - string_width)/2.0 + i) ) {
update_detector(p);
return true;
}
p->clear_envelopes();
if ( build_path_follow(p, (distance - string_width)/2.0 - i) ) {
update_detector(p);
return true;
}
std::clog << "String longer than segment, bailing" << std::endl;
return false;
}
p->starting_x = 0;
p->starting_y = 0;
int num_labels = 0;
if (p->label_spacing)
num_labels = static_cast<int> (floor(distance / (p->label_spacing + string_width)));
if (num_labels == 0)
num_labels = 1;
double ideal_spacing = distance/num_labels;
std::vector<double> ideal_label_distances;
for (double label_pos = ideal_spacing/2; label_pos < distance; label_pos += ideal_spacing)
ideal_label_distances.push_back(label_pos);
double delta = distance/100.0;
bool FoundPlacement = false;
for (std::vector<double>::const_iterator itr = ideal_label_distances.begin(); itr < ideal_label_distances.end(); ++itr)
{
std::clog << "Trying to find txt placement at distance: " << *itr << std::endl;
for (double i = 0; i < ideal_spacing; i += delta)
{
p->clear_envelopes();
// check position +- delta for valid placement
if ( build_path_follow(p, *itr - string_width/2 + i)) {
update_detector(p);
FoundPlacement = true;
break;
}
p->clear_envelopes();
if (build_path_follow(p, *itr - string_width/2 - i) ) {
update_detector(p);
FoundPlacement = true;
break;
}
}
}
return false;
if (FoundPlacement)
std::clog << "Found Placement" << string_width << " " << distance << std::endl;
return FoundPlacement;
}
bool placement_finder::find_placement_horizontal(placement *p)
{
double distance = p->get_total_distance();
if (p->path_size() == 1) // point geometry
{
if ( build_path_horizontal(p, 0) )
{
update_detector(p);
return true;
}
return false;
}
double distance = p->get_total_distance();
//~ double delta = string_width/distance;
double delta = distance/100.0;
@ -206,10 +251,6 @@ namespace mapnik
return true;
}
}
p->starting_x = 0;
p->starting_y = 0;
return false;
}
@ -226,14 +267,14 @@ namespace mapnik
}
bool placement_finder::build_path_follow(placement *p, double target_distance)
{
{
double new_x, new_y, old_x, old_y;
unsigned cur_node = 0;
double angle = 0.0;
int orientation = 0;
p->path.clear();
p->current_placement.path.clear();
double x, y;
x = y = 0.0;
@ -265,9 +306,9 @@ namespace mapnik
distance += segment_length;
if (distance > target_distance)
{
p->starting_x = new_x - dx*(distance - target_distance)/segment_length;
p->starting_y = new_y - dy*(distance - target_distance)/segment_length;
p->current_placement.starting_x = new_x - dx*(distance - target_distance)/segment_length;
p->current_placement.starting_y = new_y - dy*(distance - target_distance)/segment_length;
angle = atan2(-dy, dx);
if (angle > M_PI/2 || angle <= -M_PI/2) {
@ -282,13 +323,14 @@ namespace mapnik
break;
}
}
for (unsigned i = 0; i < p->info->num_characters(); i++)
{
character_info ci;
unsigned c;
while (distance <= 0) {
while (distance <= 0)
{
double dx, dy;
cur_node++;
@ -332,7 +374,7 @@ namespace mapnik
//Center the text on the line.
x += (((double)string_height/2.0) - 1.0)*cos(angle+M_PI/2);
y -= (((double)string_height/2.0) - 1.0)*sin(angle+M_PI/2);
if (!p->has_dimensions)
{
e.init(x, y, x + ci.width*cos(angle+M_PI), y - ci.width*sin(angle+M_PI));
@ -364,87 +406,26 @@ namespace mapnik
p->envelopes.push(e);
p->path.add_node(c, x - p->starting_x, -y + p->starting_y, (orientation == -1 ? angle + M_PI : angle));
p->current_placement.path.add_node(c, x - p->current_placement.starting_x, -y + p->current_placement.starting_y, (orientation == -1 ? angle + M_PI : angle));
distance -= ci.width;
}
p->placements.push_back(p->current_placement);
return true;
}
/*
bool placement_finder::build_path_horizontal(placement *p, double target_distance)
{
double x, y;
p->path.clear();
std::pair<double, double> string_dimensions = p->info->get_dimensions();
double string_width = string_dimensions.first;
double string_height = string_dimensions.second;
x = -string_width/2.0;
y = -string_height/2.0 + 1.0;
if (p->geom->type() == LineString)
{
std::pair<double, double> starting_pos = p->get_position_at_distance(target_distance);
p->starting_x = starting_pos.first;
p->starting_y = starting_pos.second;
}
else
{
p->geom->label_position(&p->starting_x, &p->starting_y);
// TODO:
// We would only want label position in final 'paper' coords.
// Move view and proj transforms to e.g. label_position(x,y,proj_trans,ctrans)?
double z=0;
p->proj_trans->backward(p->starting_x, p->starting_y, z);
p->ctrans->forward(&p->starting_x, &p->starting_y);
}
for (unsigned i = 0; i < p->info->num_characters(); i++)
{
character_info ci;;
ci = p->info->at(i);
unsigned c = ci.character;
p->path.add_node(c, x, y, 0.0);
Envelope<double> e;
if (p->has_dimensions)
{
e.init(p->starting_x - (p->dimensions.first/2.0), p->starting_y - (p->dimensions.second/2.0), p->starting_x + (p->dimensions.first/2.0), p->starting_y + (p->dimensions.second/2.0));
}
else
{
e.init(p->starting_x + x, p->starting_y - y, p->starting_x + x + ci.width, p->starting_y - y - ci.height);
}
if (!detector_.has_placement(e))
{
return false;
}
p->envelopes.push(e);
x += ci.width;
}
return true;
}
*/
bool placement_finder::build_path_horizontal(placement *p, double target_distance)
{
double x, y;
p->path.clear();
p->current_placement.path.clear();
std::pair<double, double> string_dimensions = p->info->get_dimensions();
double string_width = string_dimensions.first;
double string_height = string_dimensions.second;
// check if we need to wrap the string
double wrap_at = string_width + 1;
if (p->wrap_width && string_width > p->wrap_width)
@ -455,7 +436,7 @@ namespace mapnik
wrap_at = p->wrap_width;
//std::clog << "Wrapping string at" << wrap_at << std::endl;
}
// work out where our line breaks need to be
std::vector<int> line_breaks;
std::vector<double> line_widths;
@ -471,14 +452,14 @@ namespace mapnik
double word_height = 0;
for (unsigned int ii = 0; ii < p->info->num_characters(); ii++)
{
character_info ci;
character_info ci;;
ci = p->info->at(ii);
unsigned c = ci.character;
word_width += ci.width;
word_height = word_height > ci.height ? word_height : ci.height;
++line_count;
if (c == ' ')
{
last_space = ii;
@ -511,42 +492,42 @@ namespace mapnik
line_breaks.push_back(p->info->num_characters() + 1);
line_widths.push_back(string_width);
}
p->info->set_dimensions(string_width, string_height);
p->info->set_dimensions(string_width, string_height);
if (p->geom->type() == LineString)
{
std::pair<double, double> starting_pos = p->get_position_at_distance(target_distance);
p->starting_x = starting_pos.first;
p->starting_y = starting_pos.second;
p->current_placement.starting_x = starting_pos.first;
p->current_placement.starting_y = starting_pos.second;
}
else
{
p->geom->label_position(&p->starting_x, &p->starting_y);
p->geom->label_position(&p->current_placement.starting_x, &p->current_placement.starting_y);
// TODO:
// We would only want label position in final 'paper' coords.
// Move view and proj transforms to e.g. label_position(x,y,proj_trans,ctrans)?
double z=0;
p->proj_trans->backward(p->starting_x, p->starting_y, z);
p->ctrans->forward(&p->starting_x, &p->starting_y);
p->proj_trans->backward(p->current_placement.starting_x, p->current_placement.starting_y, z);
p->ctrans->forward(&p->current_placement.starting_x, &p->current_placement.starting_y);
}
double line_height = 0;
unsigned int line_number = 0;
unsigned int index_to_wrap_at = line_breaks[line_number];
double line_width = line_widths[line_number];
double x = -line_width/2.0;
double y = -string_height/2.0 + 1.0;
x = -line_width/2.0;
y = string_height/2.0;
for (unsigned i = 0; i < p->info->num_characters(); i++)
{
character_info ci;
character_info ci;;
ci = p->info->at(i);
unsigned c = ci.character;
if (i == index_to_wrap_at)
{
index_to_wrap_at = line_breaks[++line_number];
@ -558,35 +539,31 @@ namespace mapnik
}
else
{
p->path.add_node(c, x, y, 0.0);
p->current_placement.path.add_node(c, x, y, 0.0);
Envelope<double> e;
if (p->has_dimensions)
{
e.init(p->starting_x - (p->dimensions.first/2.0),
p->starting_y - (p->dimensions.second/2.0),
p->starting_x + (p->dimensions.first/2.0),
p->starting_y + (p->dimensions.second/2.0));
e.init(p->current_placement.starting_x - (p->dimensions.first/2.0), p->current_placement.starting_y - (p->dimensions.second/2.0), p->current_placement.starting_x + (p->dimensions.first/2.0), p->current_placement.starting_y + (p->dimensions.second/2.0));
}
else
{
e.init(p->starting_x + x,
p->starting_y - y,
p->starting_x + x + ci.width,
p->starting_y - y - ci.height);
e.init(p->current_placement.starting_x + x, p->current_placement.starting_y - y, p->current_placement.starting_x + x + ci.width, p->current_placement.starting_y - y - ci.height);
}
if (!detector_.has_placement(e))
{
return false;
}
p->envelopes.push(e);
}
x += ci.width;
line_height = line_height > ci.height ? line_height : ci.height;
}
p->placements.push_back(p->current_placement);
return true;
}
}
} // namespace

View file

@ -35,6 +35,7 @@ namespace mapnik
size_(size),
text_ratio_(0),
wrap_width_(0),
label_spacing_(0),
fill_(fill),
halo_fill_(Color(255,255,255)),
halo_radius_(0),
@ -48,6 +49,7 @@ namespace mapnik
size_(rhs.size_),
text_ratio_(rhs.text_ratio_),
wrap_width_(rhs.wrap_width_),
label_spacing_(rhs.label_spacing_),
fill_(rhs.fill_),
halo_fill_(rhs.halo_fill_),
halo_radius_(rhs.halo_radius_),
@ -64,6 +66,7 @@ namespace mapnik
size_ = other.size_;
text_ratio_ = other.text_ratio_;
wrap_width_ = other.wrap_width_;
label_spacing_ = other.label_spacing_;
fill_ = other.fill_;
halo_fill_ = other.halo_fill_;
halo_radius_ = other.halo_radius_;
@ -85,7 +88,7 @@ namespace mapnik
unsigned text_symbolizer::get_text_ratio() const
{
return text_ratio_;
return text_ratio_;
}
void text_symbolizer::set_text_ratio(unsigned ratio)
@ -95,12 +98,22 @@ namespace mapnik
unsigned text_symbolizer::get_wrap_width() const
{
return wrap_width_;
return wrap_width_;
}
void text_symbolizer::set_wrap_width(unsigned width)
{
wrap_width_ = width;
}
unsigned text_symbolizer::get_label_spacing() const
{
return label_spacing_;
}
void text_symbolizer::set_label_spacing(unsigned spacing)
{
label_spacing_ = spacing;
}
unsigned text_symbolizer::get_text_size() const