diff --git a/bindings/python/mapnik_text_symbolizer.cpp b/bindings/python/mapnik_text_symbolizer.cpp index b065144f8..fb3ba983e 100644 --- a/bindings/python/mapnik_text_symbolizer.cpp +++ b/bindings/python/mapnik_text_symbolizer.cpp @@ -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) diff --git a/demo/python/rundemo.py b/demo/python/rundemo.py index f30e1846a..9f161a015 100644 --- a/demo/python/rundemo.py +++ b/demo/python/rundemo.py @@ -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) diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index e9f351dba..ee065ac35 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -36,6 +36,15 @@ namespace mapnik { + + struct placement_element + { + double starting_x; + double starting_y; + + text_path path; + }; + struct placement { typedef coord_transform2 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 > envelopes; //output - double starting_x; - double starting_y; - - text_path path; + std::vector placements; + // caching output + placement_element current_placement; //helpers std::pair 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 e); - bool find_placement(placement *placement); + bool find_placements(placement *p); protected: bool find_placement_follow(placement *p); diff --git a/include/mapnik/text_path.hpp b/include/mapnik/text_path.hpp index 22358ba3b..e2dfbd16b 100644 --- a/include/mapnik/text_path.hpp +++ b/include/mapnik/text_path.hpp @@ -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_nodes_t; + typedef std::vector character_nodes_t; character_nodes_t nodes_; int itr_; @@ -120,11 +120,17 @@ namespace mapnik std::pair 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) diff --git a/include/mapnik/text_symbolizer.hpp b/include/mapnik/text_symbolizer.hpp index 257cf49b6..afa1852d0 100644 --- a/include/mapnik/text_symbolizer.hpp +++ b/include/mapnik/text_symbolizer.hpp @@ -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_; diff --git a/plugins/input/postgis/postgis.cpp b/plugins/input/postgis/postgis.cpp index 8e9103988..0d37ce26b 100644 --- a/plugins/input/postgis/postgis.cpp +++ b/plugins/input/postgis/postgis.cpp @@ -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 diff --git a/src/agg_renderer.cpp b/src/agg_renderer.cpp index 5acd3adf3..607d8431a 100644 --- a/src/agg_renderer.cpp +++ b/src/agg_renderer.cpp @@ -346,24 +346,26 @@ namespace mapnik placement text_placement(&info, &t_, &prj_trans, geom, std::pair(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 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 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 dim = ren.prepare_glyphs(&text_placement.path); - - Envelope 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 dim = ren.prepare_glyphs(&text_placement.placements[ii].path); + + Envelope 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); } } } diff --git a/src/load_map.cpp b/src/load_map.cpp index 32c7d94c1..bf614e15d 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -126,7 +126,16 @@ namespace mapnik if ( sym.first == "PointSymbolizer") { - std::cout << sym.first << "\n"; + std::string file = + sym.second.get(".file"); + std::string type = + sym.second.get(".type"); + unsigned width = + sym.second.get(".width"); + unsigned height = + sym.second.get(".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 halo_fill = sym.second.get_optional(".halo_fill"); @@ -195,6 +205,13 @@ namespace mapnik text_symbol.set_wrap_width(*wrap_width); } + // spacing between repeated labels on lines + boost::optional spacing = + sym.second.get_optional(".spacing"); + if (spacing) + { + text_symbol.set_label_spacing(*spacing); + } rule.append(text_symbol); } else if ( sym.first == "ShieldSymbolizer") diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 7a3b12ed3..1d5178d7c 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -41,26 +41,31 @@ namespace mapnik { placement::placement(string_info *info_, CoordTransform *ctrans_, const proj_transform *proj_trans_, geometry_ptr geom_, std::pair 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 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 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 (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 (floor(distance / (p->label_spacing + string_width))); + if (num_labels == 0) + num_labels = 1; + + double ideal_spacing = distance/num_labels; + std::vector 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::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 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 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 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 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 line_breaks; std::vector 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 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 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 diff --git a/src/text_symbolizer.cpp b/src/text_symbolizer.cpp index 7aec757bc..77f2d014a 100644 --- a/src/text_symbolizer.cpp +++ b/src/text_symbolizer.cpp @@ -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