diff --git a/include/mapnik/placement_finder.hpp b/include/mapnik/placement_finder.hpp index bc36c9ee3b..e4d0cfb704 100644 --- a/include/mapnik/placement_finder.hpp +++ b/include/mapnik/placement_finder.hpp @@ -87,26 +87,21 @@ namespace mapnik public: placement_finder(DetectorT & detector); - template - void find_placements(placement & p, T & path); - + //Try place a single label at the given point void find_point_placement(placement & p, double, double); + //Iterate over the given path, placing point labels with respect to label_spacing template - void find_line_placement(placement & p, T & path); + void find_point_placements(placement & p, T & path); - void clear(); - - private: + //Iterate over the given path, placing line-following labels with respect to label_spacing template - bool build_path_follow(placement & p, double target_distance, T & path); + void find_line_placements(placement & p, T & path); - template - bool build_path_horizontal(placement & p, double target_distance, T & path); - - void get_ideal_placements(placement & p, double distance, std::vector&); + void clear(); - //Helpers for find_line_placement + private: + ///Helpers for find_line_placement ///Returns a possible placement on the given line, does not test for collisions //index: index of the node the current line ends on @@ -133,6 +128,7 @@ namespace mapnik const double &x1, const double &y1, const double &x2, const double &y2, double &ix, double &iy); + ///General Internals void update_detector(placement & p); diff --git a/src/agg_renderer.cpp b/src/agg_renderer.cpp index 2953f478ef..13ef2d7a97 100644 --- a/src/agg_renderer.cpp +++ b/src/agg_renderer.cpp @@ -464,7 +464,7 @@ namespace mapnik path_type path(t_,geom,prj_trans); placement text_placement(info, sym); text_placement.avoid_edges = sym.get_avoid_edges(); - finder.find_placements(text_placement,path); + finder.find_point_placements(text_placement,path); for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ ii) { @@ -683,7 +683,7 @@ namespace mapnik } else //LINE_PLACEMENT { - finder.find_line_placement(text_placement,path); + finder.find_line_placements(text_placement,path); } for (unsigned int ii = 0; ii < text_placement.placements.size(); ++ii) diff --git a/src/placement_finder.cpp b/src/placement_finder.cpp index 43918b55ab..a4fc67e809 100644 --- a/src/placement_finder.cpp +++ b/src/placement_finder.cpp @@ -143,87 +143,69 @@ namespace mapnik } template - void placement_finder::get_ideal_placements(placement & p, double distance, std::vector & ideal_label_distances) + template + void placement_finder::find_point_placements(placement & p, T & shape_path) { - std::pair string_dimensions = p.info.get_dimensions(); - double string_width = string_dimensions.first; - - if (p.label_placement == LINE_PLACEMENT && string_width > distance) - { - //Empty! - return ; - } + unsigned cmd; + double new_x = 0.0; + double new_y = 0.0; + double old_x = 0.0; + double old_y = 0.0; + bool first = true; + + double total_distance = get_total_distance(shape_path); + shape_path.rewind(0); - int num_labels = 0; - if (p.label_spacing && p.label_placement == LINE_PLACEMENT) + if (distance == 0) //Point data, not a line { - num_labels = static_cast (floor(distance / (p.label_spacing + string_width))); - } - else if (p.label_spacing && p.label_placement == POINT_PLACEMENT) - { - num_labels = static_cast (floor(distance / p.label_spacing)); + double x, y; + shape_path.vertex(&x,&y); + find_point_placement(p, x, y); + return; } + int num_labels = 1; + if (p.label_spacing > 0) + num_labels = static_cast (floor(total_distance / p.label_spacing)); + if (p.force_odd_labels && num_labels%2 == 0) num_labels--; if (num_labels <= 0) num_labels = 1; - - - double ideal_spacing = distance/num_labels; - double middle; //try draw text centered - if (p.label_placement == LINE_PLACEMENT) - middle = (distance / 2.0) - (string_width/2.0); - else // (p.label_placement == point_placement) - middle = distance / 2.0; - - if (num_labels % 2) //odd amount of labels + double distance = 0.0; // distance from last label + double spacing = total_distance / num_labels; + double target_distance = spacing / 2; // first label should be placed at half the spacing + + while (!agg::is_stop(cmd = shape_path.vertex(&new_x,&new_y))) //For each node in the shape { - for (int a = 0; a < (num_labels+1)/2; a++) + + if (first || agg::is_move_to(cmd)) //Don't do any processing if it is the first node { - ideal_label_distances.push_back(middle - (a*ideal_spacing)); - - if (a != 0) - ideal_label_distances.push_back(middle + (a*ideal_spacing)); + first = false; } - } - else //even amount of labels - { - for (int a = 0; a < num_labels/2; a++) + else { - ideal_label_distances.push_back(middle - (ideal_spacing/2.0) - (a*ideal_spacing)); - ideal_label_distances.push_back(middle + (ideal_spacing/2.0) + (a*ideal_spacing)); - } - } - - if (p.label_position_tolerance == 0) - { - p.label_position_tolerance = unsigned(ideal_spacing/2.0); - } - } - - template - template - void placement_finder::find_placements(placement & p, T & shape_path) - { - double distance = get_total_distance(shape_path); - std::vector ideal_label_distances; - get_ideal_placements(p,distance,ideal_label_distances); - std::vector::const_iterator itr = ideal_label_distances.begin(); - std::vector::const_iterator end = ideal_label_distances.end(); - for (; itr != end; ++itr) - { + //Add the length of this segment to the total we have saved up + double segment_length = sqrt(pow(old_x-new_x,2) + pow(old_y-new_y,2)); //Pythagoras + distance += segment_length; + + //While we have enough distance to place text in + while (distance > target_distance) + { + //Try place at the specified place + double new_weight = (segment_length - (distance - target_distance))/segment_length; + find_point_placement(p, old_x + (new_x-old_x)*new_weight, old_y + (new_y-old_y)*new_weight); - if ((p.label_placement == LINE_PLACEMENT && - build_path_follow(p, *itr , shape_path ) ) || - (p.label_placement == POINT_PLACEMENT && - build_path_horizontal(p, *itr, shape_path)) ) - { - update_detector(p); - break; + distance -= target_distance; //Consume the spacing gap we have used up + target_distance = spacing; //Need to reset the target_distance as it is spacing/2 for the first label. + } } - } + + old_x = new_x; + old_y = new_y; + } + } template @@ -376,7 +358,7 @@ namespace mapnik template template - void placement_finder::find_line_placement(placement & p, PathT & shape_path) + void placement_finder::find_line_placements(placement & p, PathT & shape_path) { unsigned cmd; double new_x = 0.0; @@ -437,7 +419,7 @@ namespace mapnik num_labels = 1; //Now we know how many labels we are going to place, calculate the spacing so that they will get placed evenly - double spacing = (total_distance / num_labels); + double spacing = total_distance / num_labels; double target_distance = (spacing - string_width) / 2; // first label should be placed at half the spacing //Calculate or read out the tolerance @@ -843,334 +825,6 @@ namespace mapnik } } - template - template - bool placement_finder::build_path_follow(placement & p, - double target_distance, - PathT & shape_path) - { - double new_x = 0.0; - double new_y = 0.0; - double old_x = 0.0; - double old_y = 0.0; - double next_char_x = 0.0; - double next_char_y = 0.0; - - double angle = 0.0; - int orientation = 0; - double displacement = boost::tuples::get<1>(p.displacement_); // displace by dy - - std::auto_ptr current_placement(new placement_element); - - double x = 0.0; - double y = 0.0; - - double distance = 0.0; - - std::pair string_dimensions = p.info.get_dimensions(); - double string_height = string_dimensions.second; - - // find the segment that our text should start on - shape_path.rewind(0); - - unsigned cmd; - bool first = true; - while (!agg::is_stop(cmd = shape_path.vertex(&new_x,&new_y))) - { - if (first || agg::is_move_to(cmd)) - { - first = false; - } - else - { - double dx = new_x - old_x; - double dy = new_y - old_y; - double segment_length = sqrt(dx*dx + dy*dy); - distance += segment_length; - if (distance > target_distance) - { - current_placement->starting_x = new_x - dx*(distance - target_distance)/segment_length; - current_placement->starting_y = new_y - dy*(distance - target_distance)/segment_length; - - // angle text starts at and orientation - angle = atan2(-dy, dx); - orientation = (angle > 0.55*M_PI || angle < -0.45*M_PI) ? -1 : 1; - - distance -= target_distance; - - break; - } - } - old_x = new_x; - old_y = new_y; - } - - // now find the placement of each character starting from our initial segment - // determined above - double last_angle = angle; - for (unsigned i = 0; i < p.info.num_characters(); ++i) - { - character_info ci; - unsigned c; - - // grab the next character according to the orientation - ci = orientation > 0 ? p.info.at(i) : p.info.at(p.info.num_characters() - i - 1); - c = ci.character; - - double angle_delta = 0; - - // if the distance remaining in this segment is less than the character width - // move to the next segment - if (distance <= ci.width) - { - last_angle = angle; - while (distance <= ci.width) - { - double dx, dy; - old_x = new_x; - old_y = new_y; - - if (agg::is_stop(shape_path.vertex(&new_x,&new_y))) - return false; - dx = new_x - old_x; - dy = new_y - old_y; - - angle = atan2(-dy, dx ); - distance += sqrt(dx*dx+dy*dy); - } - // since our rendering angle has changed then check against our - // max allowable angle change. - angle_delta = last_angle - angle; - // normalise between -180 and 180 - while (angle_delta > M_PI) - angle_delta -= 2*M_PI; - while (angle_delta < -M_PI) - angle_delta += 2*M_PI; - if (p.max_char_angle_delta > 0 && fabs(angle_delta) > p.max_char_angle_delta*(M_PI/180)) - { - return false; - } - } - - Envelope e; - if (p.has_dimensions) - { - e.init(x, y, x + p.dimensions.first, y + p.dimensions.second); - } - - double render_angle = angle; - - x = new_x - (distance)*cos(angle); - y = new_y + (distance)*sin(angle); - //Center the text on the line, unless displacement != 0 - if (displacement == 0.0) { - x -= (((double)string_height/2.0) - 1.0)*cos(render_angle+M_PI/2); - y += (((double)string_height/2.0) - 1.0)*sin(render_angle+M_PI/2); - } else if (displacement*orientation > 0.0) { - x -= ((fabs(displacement) - (double)string_height) + 1.0)*cos(render_angle+M_PI/2); - y += ((fabs(displacement) - (double)string_height) + 1.0)*sin(render_angle+M_PI/2); - } else { // displacement < 0 - x -= ((fabs(displacement) + (double)string_height) - 1.0)*cos(render_angle+M_PI/2); - y += ((fabs(displacement) + (double)string_height) - 1.0)*sin(render_angle+M_PI/2); - } - distance -= ci.width; - next_char_x = ci.width*cos(render_angle); - next_char_y = ci.width*sin(render_angle); - - double render_x = x; - double render_y = y; - - if (!p.has_dimensions) - { - // put four corners of the letter into envelope - e.init(render_x, render_y, render_x + ci.width*cos(render_angle), render_y - ci.width*sin(render_angle)); - e.expand_to_include(render_x - ci.height*sin(render_angle), render_y - ci.height*cos(render_angle)); - e.expand_to_include(render_x + (ci.width*cos(render_angle) - ci.height*sin(render_angle)), - render_y - (ci.width*sin(render_angle) + ci.height*cos(render_angle))); - } - - if (!dimensions_.intersects(e) || - !detector_.has_placement(e, p.info.get_string(), p.minimum_distance)) - { - return false; - } - - if (p.avoid_edges && !dimensions_.contains(e)) - { - return false; - } - - p.envelopes.push(e); - - if (orientation < 0) - { - // rotate in place - render_x += ci.width*cos(render_angle) - (string_height-2)*sin(render_angle); - render_y -= ci.width*sin(render_angle) + (string_height-2)*cos(render_angle); - render_angle += M_PI; - } - - - current_placement->add_node(c,render_x - current_placement->starting_x, - -render_y + current_placement->starting_y, - render_angle); - x += next_char_x; - y -= next_char_y; - } - - p.placements.push_back(current_placement.release()); - - return true; - } - - template - template - bool placement_finder::build_path_horizontal(placement & p, double target_distance, PathT & shape_path) - { - double x, y; - std::auto_ptr current_placement(new placement_element); - - 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) - { - if (p.text_ratio) - for (int i = 1; ((wrap_at = string_width/i)/(string_height*i)) > p.text_ratio && (string_width/i) > p.wrap_width; ++i); - else - wrap_at = p.wrap_width; - } - - // work out where our line breaks need to be - std::vector line_breaks; - std::vector line_widths; - if (wrap_at < string_width && p.info.num_characters() > 0) - { - int line_count=0; - int last_space = 0; - string_width = 0; - string_height = 0; - double line_width = 0; - double line_height = 0; - double word_width = 0; - double word_height = 0; - for (unsigned int ii = 0; ii < p.info.num_characters(); ii++) - { - 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; - line_width += word_width; - line_height = line_height > word_height ? line_height : word_height; - word_width = 0; - word_height = 0; - } - if (line_width > 0 && line_width > wrap_at) - { - string_width = string_width > line_width ? string_width : line_width; - string_height += line_height; - line_breaks.push_back(last_space); - line_widths.push_back(line_width); - ii = last_space; - line_count = 0; - line_width = 0; - line_height = 0; - word_width = 0; - word_height = 0; - } - } - line_width += word_width; - string_width = string_width > line_width ? string_width : line_width; - line_breaks.push_back(p.info.num_characters() + 1); - line_widths.push_back(line_width); - } - if (line_breaks.size() == 0) - { - line_breaks.push_back(p.info.num_characters() + 1); - line_widths.push_back(string_width); - } - - p.info.set_dimensions(string_width, string_height); - - std::pair starting_pos = - get_position_at_distance(target_distance,shape_path); - current_placement->starting_x = starting_pos.first; - current_placement->starting_y = starting_pos.second; - - 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]; - - x = -line_width/2.0 - 1.0; - y = -string_height/2.0 + 1.0; - - for (unsigned i = 0; i < p.info.num_characters(); i++) - { - 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]; - line_width = line_widths[line_number]; - y -= line_height; - x = -line_width/2.0; - line_height = 0; - continue; - } - else - { - current_placement->add_node(c, x, y, 0.0); - - Envelope e; - if (p.has_dimensions) - { - e.init(current_placement->starting_x - (p.dimensions.first/2.0), - current_placement->starting_y - (p.dimensions.second/2.0), - current_placement->starting_x + (p.dimensions.first/2.0), - current_placement->starting_y + (p.dimensions.second/2.0)); - } - else - { - e.init(current_placement->starting_x + x, - current_placement->starting_y - y, - current_placement->starting_x + x + ci.width, - current_placement->starting_y - y - ci.height); - } - - if (!dimensions_.intersects(e) || - !detector_.has_placement(e, p.info.get_string(), p.minimum_distance)) - { - return false; - } - - if (p.avoid_edges && !dimensions_.contains(e)) - { - return false; - } - - p.envelopes.push(e); - } - x += ci.width; - line_height = line_height > ci.height ? line_height : ci.height; - } - p.placements.push_back(current_placement.release()); - - return true; - } - template void placement_finder::clear() { @@ -1181,7 +835,7 @@ namespace mapnik typedef label_collision_detector4 DetectorType; template class placement_finder; - template void placement_finder::find_placements (placement&, PathType & ); - template void placement_finder::find_line_placement (placement&, PathType & ); + template void placement_finder::find_point_placements (placement&, PathType & ); + template void placement_finder::find_line_placements (placement&, PathType & ); } // namespace