From a2c2ad40e2692b84a76c63df17555e607bc63d66 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Wed, 15 Feb 2012 20:05:39 +0100 Subject: [PATCH] Make markers_placement a bit simpler and add sanity checks. refs #974. --- include/mapnik/markers_placement.hpp | 31 ++++++- src/markers_placement.cpp | 134 ++++++++++++++++----------- 2 files changed, 108 insertions(+), 57 deletions(-) diff --git a/include/mapnik/markers_placement.hpp b/include/mapnik/markers_placement.hpp index d5c385c12..a870c14ab 100644 --- a/include/mapnik/markers_placement.hpp +++ b/include/mapnik/markers_placement.hpp @@ -35,25 +35,52 @@ template class markers_placement : boost::noncopyable { public: + /** Constructor for markers_placement object. + * \param locator Path along which markers are placed (type: vertex source) + * \param size Size of the marker + * \param detector Collision detection + * \param spacing Distance between markers. If the value is negative it is + * converted to a positive value with similar magnitude, but + * choosen to optimize marker placement. 0 = no markers + */ markers_placement(Locator &locator, box2d size, Detector &detector, double spacing, double max_error, bool allow_overlap); + /** Start again at first marker. + * \note Returns the same list of markers only works when they were NOT added + * to the detector. + */ void rewind(); + /** Get a point where the marker should be placed. + * Each time this function is called a new point is returned. + * \param x Return value for x position + * \param y Return value for x position + * \param angle Return value for rotation angle + * \param add_to_detector Add selected position to detector + * \return True if a place is found, false if none is found. + */ bool get_point(double *x, double *y, double *angle, bool add_to_detector = true); private: Locator &locator_; box2d size_; Detector &detector_; double spacing_; + double max_error_; + bool allow_overlap_; + bool done_; double last_x, last_y; double next_x, next_y; /** If a marker could not be placed at the exact point where it should * go the next marker's distance will be a bit lower. */ double error_; - double max_error_; + double spacing_left_; unsigned marker_nr_; - bool allow_overlap_; + + /** Rotates the size_ box and translates the position. */ box2d perform_transform(double angle, double dx, double dy); + /** Automatically chooses spacing. */ double find_optimal_spacing(double s); + /** Set spacing_left_, adjusts error_ and performs sanity checks. */ + void set_spacing_left(double sl, bool allow_negative=false); }; } diff --git a/src/markers_placement.cpp b/src/markers_placement.cpp index f9667ed40..0ac9c9c65 100644 --- a/src/markers_placement.cpp +++ b/src/markers_placement.cpp @@ -12,17 +12,8 @@ namespace mapnik { - - -/** Constructor for markers_placement object. - * \param locator Path along which markers are placed (type: vertex source) - * \param size Size of the marker - * \param detector Collision detection - * \param spacing Distance between markers. If the value is negative it is - * converted to a positive value with similar magnitude, but - * choosen to optimize marker placement. 0 = no markers - */ -template markers_placement::markers_placement( +template +markers_placement::markers_placement( Locator &locator, box2d size, Detector &detector, double spacing, double max_error, bool allow_overlap) : locator_(locator), size_(size), detector_(detector), max_error_(max_error), allow_overlap_(allow_overlap) { @@ -36,8 +27,8 @@ template markers_placement double markers_placement::find_optimal_spacing(double s) +template +double markers_placement::find_optimal_spacing(double s) { rewind(); //Calculate total path length @@ -62,11 +53,9 @@ template double markers_placement void markers_placement::rewind() + +template +void markers_placement::rewind() { locator_.rewind(0); //Get first point @@ -77,54 +66,64 @@ template void markers_placement bool markers_placement::get_point( +template +bool markers_placement::get_point( double *x, double *y, double *angle, bool add_to_detector) { if (done_) return false; unsigned cmd; - double spacing_left; + + /* This functions starts at the position of the previous marker, + walks along the path, counting how far it has to go in spacing_left. + If one marker can't be placed at the position it should go to it is + moved a bit. The error is compensated for in the next call to this + function. + + error > 0: Marker too near to the end of the path. + error = 0: Perfect position. + error < 0: Marker too near to the beginning of the path. + */ + if (marker_nr_++ == 0) { //First marker - spacing_left = spacing_ / 2; + spacing_left_ = spacing_ / 2; } else { - spacing_left = spacing_; + spacing_left_ = spacing_; } - spacing_left -= error_; + spacing_left_ -= error_; error_ = 0; + //Loop exits when a position is found or when no more segments are available while (true) { - //Loop exits when a position is found or when no more segments are available - if (spacing_left < size_.width()/2) + //Do not place markers too close to the beginning of a segment + if (spacing_left_ < size_.width()/2) { - //Do not place markers to close to the beginning of a segment - error_ += size_.width()/2 - spacing_left; - spacing_left = size_.width()/2; + set_spacing_left(size_.width()/2); //Only moves forward } + //Error for this marker is too large. Skip to the next position. if (abs(error_) > max_error_ * spacing_) { - spacing_left += spacing_ - error_; + if (error_ > spacing_) { + error_ = 0; //Avoid moving backwards +#ifdef MAPNIK_DEBUG + std::cerr << "WARNING: Extremely large error in markers_placement. Please file a bug report.\n"; +#endif + } + spacing_left_ += spacing_ - error_; error_ = 0; } double dx = next_x - last_x; double dy = next_y - last_y; - double d = std::sqrt(dx * dx + dy * dy); - if (d <= spacing_left) + double segment_length = std::sqrt(dx * dx + dy * dy); + if (segment_length <= spacing_left_) { //Segment is to short to place marker. Find next segment - spacing_left -= d; + spacing_left_ -= segment_length; last_x = next_x; last_y = next_y; while (agg::is_move_to(cmd = locator_.vertex(&next_x, &next_y))) @@ -140,14 +139,20 @@ template bool markers_placement spacing_left + - error is small enough + - at least half a marker fits into this segment + */ + //Check if marker really fits in this segment - if (d < size_.width()) + if (segment_length < size_.width()) { //Segment to short => Skip this segment - error_ += d + size_.width()/2 - spacing_left; - spacing_left = d + size_.width()/2; + set_spacing_left(segment_length + size_.width()/2); //Only moves forward continue; - } else if (d - spacing_left < size_.width()/2) + } else if (segment_length - spacing_left_ < size_.width()/2) { //Segment is long enough, but we are to close to the end @@ -156,26 +161,23 @@ template bool markers_placement box = perform_transform(*angle, *x, *y); if (!allow_overlap_ && !detector_.has_placement(box)) { - //10.0 is choosen arbitrarily - error_ += spacing_ * max_error_ / 10.0; - spacing_left += spacing_ * max_error_ / 10.0; + //10.0 is the approxmiate number of positions tried and choosen arbitrarily + set_spacing_left(spacing_left_ + spacing_ * max_error_ / 10.0); //Only moves forward continue; } if (add_to_detector) detector_.insert(box); @@ -185,8 +187,9 @@ template bool markers_placement box2d markers_placement::perform_transform(double angle, double dx, double dy) + +template +box2d markers_placement::perform_transform(double angle, double dx, double dy) { double c = cos(angle), s = sin(angle); double x1 = size_.minx(); @@ -202,6 +205,27 @@ template box2d markers_placement(x1_, y1_, x2_, y2_); } +template +void markers_placement::set_spacing_left(double sl, bool allow_negative) +{ + double delta_error = sl - spacing_left_; + if (!allow_negative && delta_error < 0) + { +#ifdef MAPNIK_DEBUG + std::cerr << "WARNING: Unexpected negative error in markers_placement. Please file a bug report.\n"; +#endif + return; + } +#ifdef MAPNIK_DEBUG + if (delta_error == 0.0) + { + std::cerr << "WARNING: Not moving at all in set_spacing_left()! Please file a bug report.\n"; + } +#endif + error_ += delta_error; + spacing_left_ = sl; +} + typedef coord_transform2 path_type; template class markers_placement;