From 0aad860a7543c883e4c6ab12404e13b301aa58e8 Mon Sep 17 00:00:00 2001 From: jhollinger2 Date: Sun, 3 Aug 2014 16:10:21 -0400 Subject: [PATCH] Add repeat-distance to text symbolizer properties. Update collision detector to handle minimum-distance and repeat-distance seperately. Update placement_finder to use repeat-distance. Update group symbolizer to handle minimum-distance and repeat-distance. --- bindings/python/mapnik_text_placement.cpp | 1 + include/mapnik/label_collision_detector.hpp | 34 +++++++++++---- .../process_group_symbolizer.hpp | 42 +++++++------------ include/mapnik/text/placement_finder.hpp | 2 +- include/mapnik/text/text_properties.hpp | 2 + src/group/group_symbolizer_helper.cpp | 5 ++- src/text/placement_finder.cpp | 15 ++++--- src/text/text_properties.cpp | 7 ++++ .../styles/group-symbolizer-line-1.xml | 6 +-- .../styles/group-symbolizer-line-2.xml | 8 ++-- 10 files changed, 72 insertions(+), 50 deletions(-) diff --git a/bindings/python/mapnik_text_placement.cpp b/bindings/python/mapnik_text_placement.cpp index d588dd245..cc6be4811 100644 --- a/bindings/python/mapnik_text_placement.cpp +++ b/bindings/python/mapnik_text_placement.cpp @@ -424,6 +424,7 @@ void export_text_placement() .def_readwrite("label_position_tolerance", &text_symbolizer_properties::label_position_tolerance) .def_readwrite("avoid_edges", &text_symbolizer_properties::avoid_edges) .def_readwrite("minimum_distance", &text_symbolizer_properties::minimum_distance) + .def_readwrite("repeat_distance", &text_symbolizer_properties::repeat_distance) .def_readwrite("minimum_padding", &text_symbolizer_properties::minimum_padding) .def_readwrite("minimum_path_length", &text_symbolizer_properties::minimum_path_length) .def_readwrite("maximum_angle_char_delta", &text_symbolizer_properties::max_char_angle_delta) diff --git a/include/mapnik/label_collision_detector.hpp b/include/mapnik/label_collision_detector.hpp index af5eef979..682c3708e 100644 --- a/include/mapnik/label_collision_detector.hpp +++ b/include/mapnik/label_collision_detector.hpp @@ -158,29 +158,47 @@ public: return true; } - bool has_placement(box2d const& box, mapnik::value_unicode_string const& text, double distance) + bool has_placement(box2d const& box, double minimum_distance) { - box2d bigger_box(box.minx() - distance, box.miny() - distance, box.maxx() + distance, box.maxy() + distance); - tree_t::query_iterator itr = tree_.query_in_box(bigger_box); + box2d const& minimum_box = (minimum_distance > 0 + ? box2d(box.minx() - minimum_distance, box.miny() - minimum_distance, + box.maxx() + minimum_distance, box.maxy() + minimum_distance) + : box); + + tree_t::query_iterator itr = tree_.query_in_box(minimum_box); tree_t::query_iterator end = tree_.query_end(); for (;itr != end; ++itr) { - if (itr->box.intersects(box) || (text == itr->text && itr->box.intersects(bigger_box))) + if (itr->box.intersects(minimum_box)) + { return false; + } } return true; } - bool has_point_placement(box2d const& box, double distance) + bool has_placement(box2d const& box, double minimum_distance, mapnik::value_unicode_string const& text, double repeat_distance) { - box2d bigger_box(box.minx() - distance, box.miny() - distance, box.maxx() + distance, box.maxy() + distance); - tree_t::query_iterator itr = tree_.query_in_box(bigger_box); + box2d const& minimum_box = (minimum_distance > 0 + ? box2d(box.minx() - minimum_distance, box.miny() - minimum_distance, + box.maxx() + minimum_distance, box.maxy() + minimum_distance) + : box); + + box2d const& repeat_box = (repeat_distance > 0 + ? box2d(box.minx() - repeat_distance, box.miny() - repeat_distance, + box.maxx() + repeat_distance, box.maxy() + repeat_distance) + : box); + + tree_t::query_iterator itr = tree_.query_in_box(repeat_distance > minimum_distance ? repeat_box : minimum_box); tree_t::query_iterator end = tree_.query_end(); for ( ;itr != end; ++itr) { - if (itr->box.intersects(bigger_box)) return false; + if (itr->box.intersects(minimum_box) || (text == itr->text && itr->box.intersects(repeat_box))) + { + return false; + } } return true; diff --git a/include/mapnik/renderer_common/process_group_symbolizer.hpp b/include/mapnik/renderer_common/process_group_symbolizer.hpp index c4cc390d3..3aad3925b 100644 --- a/include/mapnik/renderer_common/process_group_symbolizer.hpp +++ b/include/mapnik/renderer_common/process_group_symbolizer.hpp @@ -297,38 +297,28 @@ void render_group_symbolizer(group_symbolizer const& sym, common.scale_factor_, common.t_, *common.detector_, clipping_extent); - // determine if we should be tracking repeat distance - bool check_repeat = (helper.get_properties().minimum_distance > 0); - for (size_t i = 0; i < matches.size(); ++i) { - if (check_repeat) + group_rule_ptr match_rule = matches[i].first; + feature_ptr match_feature = matches[i].second; + value_unicode_string rpt_key_value = ""; + + // get repeat key from matched group rule + expression_ptr rpt_key_expr = match_rule->get_repeat_key(); + + // if no repeat key was defined, use default from group symbolizer + if (!rpt_key_expr) { - group_rule_ptr match_rule = matches[i].first; - feature_ptr match_feature = matches[i].second; - value_unicode_string rpt_key_value = ""; - - // get repeat key from matched group rule - expression_ptr rpt_key_expr = match_rule->get_repeat_key(); - - // if no repeat key was defined, use default from group symbolizer - if (!rpt_key_expr) - { - rpt_key_expr = get(sym, keys::repeat_key); - } - - // evalute the repeat key with the matched sub feature if we have one - if (rpt_key_expr) - { - rpt_key_value = boost::apply_visitor(evaluate(*match_feature,common.vars_), - *rpt_key_expr).to_unicode(); - } - helper.add_box_element(layout_manager.offset_box_at(i), rpt_key_value); + rpt_key_expr = get(sym, keys::repeat_key); } - else + + // evalute the repeat key with the matched sub feature if we have one + if (rpt_key_expr) { - helper.add_box_element(layout_manager.offset_box_at(i)); + rpt_key_value = boost::apply_visitor(evaluate(*match_feature,common.vars_), + *rpt_key_expr).to_unicode(); } + helper.add_box_element(layout_manager.offset_box_at(i), rpt_key_value); } pixel_position_list positions = helper.get(); diff --git a/include/mapnik/text/placement_finder.hpp b/include/mapnik/text/placement_finder.hpp index 2bfb774bc..f7afd8979 100644 --- a/include/mapnik/text/placement_finder.hpp +++ b/include/mapnik/text/placement_finder.hpp @@ -72,7 +72,7 @@ private: // Adjusts user defined spacing to place an integer number of labels. double get_spacing(double path_length, double layout_width) const; // Checks for collision. - bool collision(box2d const& box) const; + bool collision(box2d const& box, const value_unicode_string &repeat_key = "") const; // Adds marker to glyph_positions and to collision detector. Returns false if there is a collision. bool add_marker(glyph_positions_ptr glyphs, pixel_position const& pos) const; // Maps upright==auto, left_only and right_only to left,right to simplify processing. diff --git a/include/mapnik/text/text_properties.hpp b/include/mapnik/text/text_properties.hpp index a647ad969..036be0f85 100644 --- a/include/mapnik/text/text_properties.hpp +++ b/include/mapnik/text/text_properties.hpp @@ -128,6 +128,7 @@ struct text_properties_expressions symbolizer_base::value_type label_spacing = 0.0; symbolizer_base::value_type label_position_tolerance = 0.0; symbolizer_base::value_type avoid_edges = false; + symbolizer_base::value_type repeat_distance = 0.0; symbolizer_base::value_type minimum_distance = 0.0; symbolizer_base::value_type minimum_padding = 0.0; symbolizer_base::value_type minimum_path_length = 0.0; @@ -170,6 +171,7 @@ struct MAPNIK_DECL text_symbolizer_properties // distance the label can be moved on the line to fit, if 0 the default is used double label_position_tolerance; bool avoid_edges; + double repeat_distance; double minimum_distance; double minimum_padding; double minimum_path_length; diff --git a/src/group/group_symbolizer_helper.cpp b/src/group/group_symbolizer_helper.cpp index a03e13150..be9b40a7a 100644 --- a/src/group/group_symbolizer_helper.cpp +++ b/src/group/group_symbolizer_helper.cpp @@ -164,9 +164,10 @@ bool group_symbolizer_helper::collision(const box2d &box, const value_un !query_extent_.contains(box + (scale_factor_ * placement_->properties.minimum_padding))) || (!placement_->properties.allow_overlap && - ((repeat_key.length() == 0 && !detector_.has_point_placement(box, placement_->properties.minimum_distance * scale_factor_)) + ((repeat_key.length() == 0 && !detector_.has_placement(box, placement_->properties.minimum_distance * scale_factor_)) || - (repeat_key.length() > 0 && !detector_.has_placement(box, repeat_key, placement_->properties.minimum_distance * scale_factor_)))) + (repeat_key.length() > 0 && !detector_.has_placement(box, placement_->properties.minimum_distance * scale_factor_, + repeat_key, placement_->properties.repeat_distance * scale_factor_)))) ) { return true; diff --git a/src/text/placement_finder.cpp b/src/text/placement_finder.cpp index cad774ada..32608bfc3 100644 --- a/src/text/placement_finder.cpp +++ b/src/text/placement_finder.cpp @@ -120,8 +120,8 @@ bool placement_finder::find_point_placement(pixel_position const& pos) box2d bbox = layout.bounds(); bbox.re_center(layout_center.x, layout_center.y); - // For point placements it is faster to just check the bounding box. - if (collision(bbox)) return false; + /* For point placements it is faster to just check the bounding box. */ + if (collision(bbox, layouts_.text())) return false; if (layout.num_lines()) bboxes.push_back(std::move(bbox)); @@ -309,7 +309,7 @@ bool placement_finder::single_line_placement(vertex_cache &pp, text_upright_e or cluster_offset.y -= rot.sin * glyph.advance(); box2d bbox = get_bbox(layout, glyph, pos, rot); - if (collision(bbox)) return false; + if (collision(bbox, layouts_.text())) return false; bboxes.push_back(std::move(bbox)); glyphs->push_back(glyph, pos, rot); } @@ -376,7 +376,7 @@ double placement_finder::get_spacing(double path_length, double layout_width) co return path_length / num_labels; } -bool placement_finder::collision(const box2d &box) const +bool placement_finder::collision(const box2d &box, const value_unicode_string &repeat_key) const { if (!detector_.extent().intersects(box) || @@ -386,7 +386,10 @@ bool placement_finder::collision(const box2d &box) const !extent_.contains(box + (scale_factor_ * info_.properties.minimum_padding))) || (!info_.properties.allow_overlap && - !detector_.has_point_placement(box, info_.properties.minimum_distance * scale_factor_)) + ((repeat_key.length() == 0 && !detector_.has_placement(box, info_.properties.minimum_distance * scale_factor_)) + || + (repeat_key.length() > 0 && !detector_.has_placement(box, info_.properties.minimum_distance * scale_factor_, + repeat_key, info_.properties.repeat_distance * scale_factor_)))) ) { return true; @@ -410,7 +413,7 @@ bool placement_finder::add_marker(glyph_positions_ptr glyphs, pixel_position con box2d bbox = marker_box_; bbox.move(real_pos.x, real_pos.y); glyphs->set_marker(marker_, real_pos); - if (collision(bbox)) return false; + if (collision(bbox, layouts_.text())) return false; detector_.insert(bbox); return true; } diff --git a/src/text/text_properties.cpp b/src/text/text_properties.cpp index 2fa6321c1..a770dc5f8 100644 --- a/src/text/text_properties.cpp +++ b/src/text/text_properties.cpp @@ -44,6 +44,7 @@ text_symbolizer_properties::text_symbolizer_properties() label_spacing(0.0), label_position_tolerance(0.0), avoid_edges(false), + repeat_distance(0.0), minimum_distance(0.0), minimum_padding(0.0), minimum_path_length(0.0), @@ -62,6 +63,7 @@ void text_symbolizer_properties::evaluate_text_properties(feature_impl const& fe label_spacing = boost::apply_visitor(extract_value(feature,attrs), expressions.label_spacing); label_position_tolerance = boost::apply_visitor(extract_value(feature,attrs), expressions.label_position_tolerance); avoid_edges = boost::apply_visitor(extract_value(feature,attrs), expressions.avoid_edges); + repeat_distance = boost::apply_visitor(extract_value(feature,attrs), expressions.repeat_distance); minimum_distance = boost::apply_visitor(extract_value(feature,attrs), expressions.minimum_distance); minimum_padding = boost::apply_visitor(extract_value(feature,attrs), expressions.minimum_padding); minimum_path_length = boost::apply_visitor(extract_value(feature,attrs), expressions.minimum_path_length); @@ -117,6 +119,7 @@ void text_symbolizer_properties::text_properties_from_xml(xml_node const& node) set_property_from_xml(expressions.label_placement, "placement", node); set_property_from_xml(expressions.label_spacing, "spacing", node); set_property_from_xml(expressions.label_position_tolerance, "label-position-tolerance", node); + set_property_from_xml(expressions.repeat_distance, "repeat-distance", node); set_property_from_xml(expressions.minimum_distance, "minimum-distance", node); set_property_from_xml(expressions.minimum_padding, "minimum-padding", node); set_property_from_xml(expressions.minimum_path_length, "minimum-path-length", node); @@ -159,6 +162,10 @@ void text_symbolizer_properties::to_xml(boost::property_tree::ptree &node, { serialize_property("spacing", expressions.label_spacing, node); } + if (!(expressions.repeat_distance == dfl.expressions.repeat_distance) || explicit_defaults) + { + serialize_property("repeat-distance", expressions.repeat_distance, node); + } if (!(expressions.minimum_distance == dfl.expressions.minimum_distance) || explicit_defaults) { serialize_property("minimum-distance", expressions.minimum_distance, node); diff --git a/tests/visual_tests/styles/group-symbolizer-line-1.xml b/tests/visual_tests/styles/group-symbolizer-line-1.xml index d17b29c2c..4025c7521 100644 --- a/tests/visual_tests/styles/group-symbolizer-line-1.xml +++ b/tests/visual_tests/styles/group-symbolizer-line-1.xml @@ -17,7 +17,7 @@ wkt,name1,ref1,name2,ref2