From 2940de0b0be00aee1fba06702c1e3e3bdfd0ff11 Mon Sep 17 00:00:00 2001 From: Jiri Drbalek Date: Mon, 28 Jul 2014 15:05:51 +0000 Subject: [PATCH] refactor markers placements --- include/build.py | 15 +- include/mapnik/grid/grid_marker_helpers.hpp | 158 +++------ include/mapnik/marker_helpers.hpp | 136 +++----- include/mapnik/markers_placement.hpp | 308 ++++-------------- .../mapnik/markers_placements/interior.hpp | 99 ++++++ include/mapnik/markers_placements/line.hpp | 241 ++++++++++++++ include/mapnik/markers_placements/point.hpp | 157 +++++++++ src/cairo/cairo_renderer.cpp | 175 ++++------ 8 files changed, 713 insertions(+), 576 deletions(-) create mode 100644 include/mapnik/markers_placements/interior.hpp create mode 100644 include/mapnik/markers_placements/line.hpp create mode 100644 include/mapnik/markers_placements/point.hpp diff --git a/include/build.py b/include/build.py index 138853a81..5629841db 100644 --- a/include/build.py +++ b/include/build.py @@ -25,7 +25,20 @@ from glob import glob Import('env') base = './mapnik/' -subdirs = ['','svg','wkt','cairo','grid','json','util','group','text','text/placements','text/formatting'] +subdirs = [ + '', + 'svg', + 'wkt', + 'cairo', + 'grid', + 'json', + 'util', + 'group', + 'text', + 'text/placements', + 'text/formatting', + 'markers_placements' + ] if env['SVG_RENDERER']: subdirs.append('svg/output') diff --git a/include/mapnik/grid/grid_marker_helpers.hpp b/include/mapnik/grid/grid_marker_helpers.hpp index 75408eed2..1279d0565 100644 --- a/include/mapnik/grid/grid_marker_helpers.hpp +++ b/include/mapnik/grid/grid_marker_helpers.hpp @@ -80,72 +80,29 @@ struct raster_markers_rasterizer_dispatch_grid : mapnik::noncopyable marker_placement_enum placement_method = get(sym_, keys::markers_placement_type, feature_, vars_, MARKER_POINT_PLACEMENT); bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); bool allow_overlap = get(sym_, keys::allow_overlap, feature_, vars_, false); - - box2d bbox(0,0, src_.width(),src_.height()); - if (placement_method != MARKER_LINE_PLACEMENT || - path.type() == geometry_type::types::Point) + box2d bbox(0,0, src_.width(), src_.height()); + double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); + double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); + markers_placement_finder placement_finder( + placement_method, + path, + bbox, + marker_trans_, + detector_, + spacing * scale_factor_, + max_error, + allow_overlap); + double x, y, angle = .0; + while (placement_finder.get_point(x, y, angle, ignore_placement)) { - double x = 0; - double y = 0; - if (path.type() == geometry_type::types::LineString) - { - if (!label::middle_point(path, x, y)) - { - return; - } - } - else if (placement_method == MARKER_INTERIOR_PLACEMENT) - { - if (!label::interior_position(path, x, y)) - { - return; - } - } - else - { - if (!label::centroid(path, x, y)) - { - return; - } - } agg::trans_affine matrix = marker_trans_; - matrix.translate(x,y); - box2d transformed_bbox = bbox * matrix; - if (allow_overlap || - detector_.has_placement(transformed_bbox)) + matrix.rotate(angle); + matrix.translate(x, y); + render_raster_marker(matrix); + if (!placed_) { - render_raster_marker(matrix); - if (!ignore_placement) - { - detector_.insert(transformed_bbox); - } - if (!placed_) - { - pixmap_.add_feature(feature_); - placed_ = true; - } - } - } - else - { - double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); - double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); - markers_placement placement(path, bbox, marker_trans_, detector_, - spacing * scale_factor_, - max_error, - allow_overlap); - double x, y, angle; - while (placement.get_point(x, y, angle, ignore_placement)) - { - agg::trans_affine matrix = marker_trans_; - matrix.rotate(angle); - matrix.translate(x, y); - render_raster_marker(matrix); - if (!placed_) - { - pixmap_.add_feature(feature_); - placed_ = true; - } + pixmap_.add_feature(feature_); + placed_ = true; } } } @@ -239,69 +196,34 @@ struct vector_markers_rasterizer_dispatch_grid : mapnik::noncopyable bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); double opacity = get(sym_,keys::opacity, feature_, vars_, 1.0); bool allow_overlap = get(sym_, keys::allow_overlap, feature_, vars_, false); - coord2d center = bbox_.center(); agg::trans_affine_translation recenter(-center.x, -center.y); - - if (placement_method != MARKER_LINE_PLACEMENT || - path.type() == geometry_type::types::Point) + double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); + double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); + markers_placement_finder placement_finder( + placement_method, + path, + bbox_, + marker_trans_, + detector_, + spacing * scale_factor_, + max_error, + allow_overlap); + double x, y, angle = .0; + while (placement_finder.get_point(x, y, angle, ignore_placement)) { - double x = 0; - double y = 0; - if (path.type() == geometry_type::types::LineString) - { - if (!label::middle_point(path, x, y)) return; - } - else if (placement_method == MARKER_INTERIOR_PLACEMENT) - { - if (!label::interior_position(path, x, y)) return; - } - else - { - if (!label::centroid(path, x, y)) return; - } - agg::trans_affine matrix = recenter * marker_trans_; - matrix.translate(x,y); - box2d transformed_bbox = bbox_ * matrix; - if (allow_overlap || - detector_.has_placement(transformed_bbox)) + matrix.rotate(angle); + matrix.translate(x, y); + svg_renderer_.render_id(ras_, sl_, renb_, feature_.id(), matrix, opacity, bbox_); + if (!placed_) { - svg_renderer_.render_id(ras_, sl_, renb_, feature_.id(), matrix, opacity, bbox_); - if (!ignore_placement) - { - detector_.insert(transformed_bbox); - } - if (!placed_) - { - pixmap_.add_feature(feature_); - placed_ = true; - } - } - } - else - { - double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); - double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); - markers_placement placement(path, bbox_, marker_trans_, detector_, - spacing * scale_factor_, - max_error, - allow_overlap); - double x, y, angle; - while (placement.get_point(x, y, angle, ignore_placement)) - { - agg::trans_affine matrix = recenter * marker_trans_; - matrix.rotate(angle); - matrix.translate(x, y); - svg_renderer_.render_id(ras_, sl_, renb_, feature_.id(), matrix, opacity, bbox_); - if (!placed_) - { - pixmap_.add_feature(feature_); - placed_ = true; - } + pixmap_.add_feature(feature_); + placed_ = true; } } } + private: BufferType & buf_; pixfmt_type pixf_; diff --git a/include/mapnik/marker_helpers.hpp b/include/mapnik/marker_helpers.hpp index aeecec6aa..b1467f9b5 100644 --- a/include/mapnik/marker_helpers.hpp +++ b/include/mapnik/marker_helpers.hpp @@ -36,6 +36,8 @@ #include #include #include +#include +#include // agg #include "agg_ellipse.h" @@ -111,69 +113,35 @@ struct vector_markers_rasterizer_dispatch : mapnik::noncopyable bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); bool allow_overlap = get(sym_, keys::allow_overlap, feature_, vars_, false); double opacity = get(sym_,keys::opacity, feature_, vars_, 1.0); - coord2d center = bbox_.center(); agg::trans_affine_translation recenter(-center.x, -center.y); - - if (placement_method != MARKER_LINE_PLACEMENT || - path.type() == mapnik::geometry_type::types::Point) + double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); + double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); + markers_placement_finder placement_finder( + placement_method, + path, + bbox_, + marker_trans_, + detector_, + spacing * scale_factor_, + max_error, + allow_overlap); + double x, y, angle = .0; + while (placement_finder.get_point(x, y, angle, ignore_placement)) { - double x = 0; - double y = 0; - if (path.type() == mapnik::geometry_type::types::LineString) - { - if (!label::middle_point(path, x, y)) return; - } - else if (placement_method == MARKER_INTERIOR_PLACEMENT) - { - if (!label::interior_position(path, x, y)) return; - } - else - { - if (!label::centroid(path, x, y)) return; - } agg::trans_affine matrix = recenter * marker_trans_; - matrix.translate(x,y); + matrix.rotate(angle); + matrix.translate(x, y); if (snap_to_pixels_) { // https://github.com/mapnik/mapnik/issues/1316 - matrix.tx = std::floor(matrix.tx+.5); - matrix.ty = std::floor(matrix.ty+.5); - } - // TODO https://github.com/mapnik/mapnik/issues/1754 - box2d transformed_bbox = bbox_ * matrix; - - if (allow_overlap || - detector_.has_placement(transformed_bbox)) - { - svg_renderer_.render(ras_, sl_, renb_, matrix, opacity, bbox_); - if (!ignore_placement) - { - detector_.insert(transformed_bbox); - } - } - } - else - { - double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); - double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); - markers_placement placement(path, bbox_, marker_trans_, detector_, - spacing * scale_factor_, - max_error, - allow_overlap); - double x = 0; - double y = 0; - double angle = 0; - while (placement.get_point(x, y, angle, ignore_placement)) - { - - agg::trans_affine matrix = recenter * marker_trans_; - matrix.rotate(angle); - matrix.translate(x, y); - svg_renderer_.render(ras_, sl_, renb_, matrix, opacity, bbox_); + matrix.tx = std::floor(matrix.tx + .5); + matrix.ty = std::floor(matrix.ty + .5); } + svg_renderer_.render(ras_, sl_, renb_, matrix, opacity, bbox_); } } + private: BufferType & buf_; pixfmt_type pixf_; @@ -236,54 +204,24 @@ struct raster_markers_rasterizer_dispatch : mapnik::noncopyable box2d bbox_(0,0, src_.width(),src_.height()); double opacity = get(sym_, keys::opacity, feature_, vars_, 1.0); bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); - - if (placement_method != MARKER_LINE_PLACEMENT || - path.type() == mapnik::geometry_type::types::Point) + double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); + double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); + markers_placement_finder placement_finder( + placement_method, + path, + bbox_, + marker_trans_, + detector_, + spacing * scale_factor_, + max_error, + allow_overlap); + double x, y, angle = .0; + while (placement_finder.get_point(x, y, angle, ignore_placement)) { - double x = 0; - double y = 0; - if (path.type() == mapnik::geometry_type::types::LineString) - { - if (!label::middle_point(path, x, y)) return; - } - else if (placement_method == MARKER_INTERIOR_PLACEMENT) - { - if (!label::interior_position(path, x, y)) return; - } - else - { - if (!label::centroid(path, x, y)) return; - } agg::trans_affine matrix = marker_trans_; - matrix.translate(x,y); - box2d transformed_bbox = bbox_ * matrix; - - if (allow_overlap || - detector_.has_placement(transformed_bbox)) - { - render_raster_marker(matrix, opacity); - if (!ignore_placement) - { - detector_.insert(transformed_bbox); - } - } - } - else - { - double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); - double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); - markers_placement placement(path, bbox_, marker_trans_, detector_, - spacing * scale_factor_, - max_error, - allow_overlap); - double x, y, angle; - while (placement.get_point(x, y, angle, ignore_placement)) - { - agg::trans_affine matrix = marker_trans_; - matrix.rotate(angle); - matrix.translate(x, y); - render_raster_marker(matrix, opacity); - } + matrix.rotate(angle); + matrix.translate(x, y); + render_raster_marker(matrix, opacity); } } diff --git a/include/mapnik/markers_placement.hpp b/include/mapnik/markers_placement.hpp index 4cee42b6a..6a784a790 100644 --- a/include/mapnik/markers_placement.hpp +++ b/include/mapnik/markers_placement.hpp @@ -23,83 +23,55 @@ #ifndef MAPNIK_MARKERS_PLACEMENT_HPP #define MAPNIK_MARKERS_PLACEMENT_HPP -// mapnik -#include -#include -#include -#include //round -#include -#include +#include +#include +#include +#include -// agg -#include "agg_basics.h" -#include "agg_trans_affine.h" +#include +#include +#include -// stl -#include - -namespace mapnik { +namespace mapnik +{ template -class markers_placement : mapnik::noncopyable +class markers_placement_finder : mapnik::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 tr Affine transform - * \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 const& size, - agg::trans_affine const& tr, - Detector &detector, - double spacing, - double max_error, - bool allow_overlap) - : locator_(locator), - size_(size), - tr_(tr), - detector_(detector), - max_error_(max_error), - allow_overlap_(allow_overlap), - marker_width_((size_ * tr_).width()), - done_(false), - last_x(0.0), - last_y(0.0), - next_x(0.0), - next_y(0.0), - error_(0.0), - spacing_left_(0.0), - marker_nr_(0) - { - if (spacing >= 1) - { - spacing_ = spacing; - } - else - { - spacing_ = 100; - } - rewind(); - } + using markers_placement = boost::variant, + markers_line_placement, + markers_interior_placement>; - /** Start again at first marker. - * \note Returns the same list of markers only works when they were NOT added - * to the detector. - */ - void rewind() + class get_point_visitor : public boost::static_visitor + { + public: + get_point_visitor(double &x, double &y, double &angle, bool ignore_placement) + : x_(x), y_(y), angle_(angle), ignore_placement_(ignore_placement) + { + } + + template + bool operator()(T &placement) const + { + return placement.get_point(x_, y_, angle_, ignore_placement_); + } + + private: + double &x_, &y_, &angle_; + bool ignore_placement_; + }; + + markers_placement_finder(marker_placement_e placement_type, + Locator &locator, + box2d const& size, + agg::trans_affine const& tr, + Detector &detector, + double spacing, + double max_error, + bool allow_overlap) + : placement_(create(placement_type, locator, size, tr, detector, spacing, max_error, allow_overlap)) { - locator_.rewind(0); - //Get first point - done_ = agg::is_stop(locator_.vertex(&next_x, &next_y)); - last_x = next_x; - last_y = next_y; // Force request of new segment - error_ = 0.0; - marker_nr_ = 0; } /** Get a point where the marker should be placed. @@ -110,184 +82,40 @@ public: * \param ignore_placement Whether to 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 ignore_placement) + bool get_point(double &x, double &y, double &angle, bool ignore_placement) { - if (done_) - { - return false; - } - unsigned cmd; - /* 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 - marker_nr_++; - spacing_left_ = spacing_ / 2; - } - else - { - spacing_left_ = spacing_; - } - spacing_left_ -= error_; - error_ = 0.0; - double max_err_allowed = max_error_ * spacing_; - // Loop exits when a position is found or when no more segments are available - while (true) - { - // Do not place markers too close to the beginning of a segment - if (spacing_left_ < marker_width_/2) - { - set_spacing_left(marker_width_/2); //Only moves forward - } - // Error for this marker is too large. Skip to the next position. - if (std::fabs(error_) > max_err_allowed) - { - if (error_ > spacing_) - { - error_ = spacing_; // Avoid moving backwards - } - spacing_left_ += spacing_ - error_; - error_ = 0.0; - } - double dx = next_x - last_x; - double dy = next_y - last_y; - double segment_length = std::sqrt(dx * dx + dy * dy); - if (segment_length <= spacing_left_) - { - //Segment is too short to place marker. Find next segment - spacing_left_ -= segment_length; - last_x = next_x; - last_y = next_y; - while (agg::is_move_to(cmd = locator_.vertex(&next_x, &next_y))) - { - //Skip over "move" commands - last_x = next_x; - last_y = next_y; - } - if (agg::is_stop(cmd) || cmd == SEG_CLOSE) - { - done_ = true; - return false; - } - continue; //Try again - } - /* At this point we know the following things: - - segment_length > spacing_left - - error is small enough - - at least half a marker fits into this segment - */ - //Check if marker really fits in this segment - if (segment_length < marker_width_) - { - //Segment to short => Skip this segment - set_spacing_left(segment_length + marker_width_/2); //Only moves forward - continue; - } - else if (segment_length - spacing_left_ < marker_width_/2) - { - //Segment is long enough, but we are to close to the end - //Note: This function moves backwards. This could lead to an infinite - // loop when another function adds a positive offset. Therefore we - // only move backwards when there is no offset - if (error_ == 0) - { - set_spacing_left(segment_length - marker_width_/2, true); - } - else - { - //Skip this segment - set_spacing_left(segment_length + marker_width_/2); //Only moves forward - } - continue; //Force checking of max_error constraint - } - angle = std::atan2(dy, dx); - x = last_x + dx * (spacing_left_ / segment_length); - y = last_y + dy * (spacing_left_ / segment_length); - box2d box = perform_transform(angle, x, y); - if (!allow_overlap_ && !detector_.has_placement(box)) - { - //10.0 is the approximate number of positions tried and choosen arbitrarily - set_spacing_left(spacing_left_ + spacing_ * max_error_ / 10.0); //Only moves forward - continue; - } - if (!ignore_placement) - { - detector_.insert(box); - } - last_x = x; - last_y = y; - return true; - } + return boost::apply_visitor(get_point_visitor(x, y, angle, ignore_placement), placement_); } private: - Locator &locator_; - box2d size_; - agg::trans_affine tr_; - Detector &detector_; - double spacing_; - double max_error_; - bool allow_overlap_; - double marker_width_; - - bool done_; - double last_x; - double last_y; - double next_x; - double 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 spacing_left_; - unsigned marker_nr_; - - /** Rotates the size_ box and translates the position. */ - box2d perform_transform(double angle, double dx, double dy) + /** Factory function for particular placement implementations. + */ + static markers_placement create(marker_placement_e placement_type, + Locator &locator, + box2d const& size, + agg::trans_affine const& tr, + Detector &detector, + double spacing, + double max_error, + bool allow_overlap) { - double x1 = size_.minx(); - double x2 = size_.maxx(); - double y1 = size_.miny(); - double y2 = size_.maxy(); - agg::trans_affine tr = tr_ * agg::trans_affine_rotation(angle).translate(dx, dy); - double xA = x1, yA = y1, xB = x2, yB = y1, xC = x2, yC = y2, xD = x1, yD = y2; - tr.transform(&xA, &yA); - tr.transform(&xB, &yB); - tr.transform(&xC, &yC); - tr.transform(&xD, &yD); - box2d result(xA, yA, xC, yC); - result.expand_to_include(xB, yB); - result.expand_to_include(xD, yD); - return result; - } - - /** Set spacing_left_, adjusts error_ and performs sanity checks. */ - void set_spacing_left(double sl, bool allow_negative=false) - { - double delta_error = sl - spacing_left_; - if (!allow_negative && delta_error < 0) - { - MAPNIK_LOG_WARN(markers_placement) << "Unexpected negative error in markers_placement. Please file a bug report."; - return; - } - #ifdef MAPNIK_DEBUG - if (delta_error == 0.0) - { - MAPNIK_LOG_WARN(markers_placement) << "Not moving at all in set_spacing_left()! Please file a bug report."; - } - #endif - error_ += delta_error; - spacing_left_ = sl; + static const std::map const& size, + agg::trans_affine const& tr, + Detector &detector, + double spacing, + double max_error, + bool allow_overlap)>> factories = + { + { MARKER_POINT_PLACEMENT, boost::value_factory>() }, + { MARKER_INTERIOR_PLACEMENT, boost::value_factory>() }, + { MARKER_LINE_PLACEMENT, boost::value_factory>() } + }; + return factories.at(placement_type)(locator, size, tr, detector, spacing, max_error, allow_overlap); } + markers_placement placement_; }; } diff --git a/include/mapnik/markers_placements/interior.hpp b/include/mapnik/markers_placements/interior.hpp new file mode 100644 index 000000000..02d998655 --- /dev/null +++ b/include/mapnik/markers_placements/interior.hpp @@ -0,0 +1,99 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2014 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +#ifndef MAPNIK_MARKERS_PLACEMENTS_INTERIOR_HPP +#define MAPNIK_MARKERS_PLACEMENTS_INTERIOR_HPP + +#include +#include + +namespace mapnik { + +template +class markers_interior_placement : public markers_point_placement +{ +public: + markers_interior_placement( + Locator &locator, + box2d const& size, + agg::trans_affine const& tr, + Detector &detector, + double spacing, + double max_error, + bool allow_overlap) + : markers_point_placement( + locator, size, tr, detector, + spacing, max_error, allow_overlap) + { + } + + bool get_point(double &x, double &y, double &angle, bool ignore_placement) + { + if (this->done_) + { + return false; + } + + if (this->locator_.type() == mapnik::geometry_type::types::Point) + { + return markers_point_placement::get_point(x, y, angle, ignore_placement); + } + + if (this->locator_.type() == mapnik::geometry_type::types::LineString) + { + if (!label::middle_point(this->locator_, x, y)) + { + this->done_ = true; + return false; + } + } + else + { + if (!label::interior_position(this->locator_, x, y)) + { + this->done_ = true; + return false; + } + } + + angle = 0; + + box2d box = this->perform_transform(angle, x, y); + + if (!this->allow_overlap_ && !this->detector_.has_placement(box)) + { + return false; + } + + if (!ignore_placement) + { + this->detector_.insert(box); + } + + this->done_ = true; + return true; + } +}; + +} + +#endif // MAPNIK_MARKERS_PLACEMENTS_INTERIOR_HPP diff --git a/include/mapnik/markers_placements/line.hpp b/include/mapnik/markers_placements/line.hpp new file mode 100644 index 000000000..3244ede79 --- /dev/null +++ b/include/mapnik/markers_placements/line.hpp @@ -0,0 +1,241 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2014 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +#ifndef MAPNIK_MARKERS_PLACEMENTS_LINE_HPP +#define MAPNIK_MARKERS_PLACEMENTS_LINE_HPP + +#include +#include +#include +#include //round + +#include + +namespace mapnik { + +template +class markers_line_placement : public markers_point_placement +{ +public: + markers_line_placement(Locator &locator, + box2d const& size, + agg::trans_affine const& tr, + Detector &detector, + double spacing, + double max_error, + bool allow_overlap) + : markers_point_placement( + locator, size, tr, detector, + spacing, max_error, allow_overlap), + last_x(0.0), + last_y(0.0), + next_x(0.0), + next_y(0.0), + error_(0.0), + spacing_left_(0.0), + marker_nr_(0) + { + if (spacing >= 1) + { + this->spacing_ = spacing; + } + else + { + this->spacing_ = 100; + } + rewind(); + } + + void rewind() + { + this->locator_.rewind(0); + //Get first point + this->done_ = agg::is_stop(this->locator_.vertex(&next_x, &next_y)); + last_x = next_x; + last_y = next_y; // Force request of new segment + error_ = 0.0; + marker_nr_ = 0; + } + + bool get_point(double &x, double &y, double &angle, bool ignore_placement) + { + if (this->done_) + { + return false; + } + + if (this->locator_.type() == mapnik::geometry_type::types::Point) + { + return markers_point_placement::get_point(x, y, angle, ignore_placement); + } + + unsigned cmd; + /* 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 + marker_nr_++; + this->spacing_left_ = this->spacing_ / 2; + } + else + { + this->spacing_left_ = this->spacing_; + } + spacing_left_ -= error_; + error_ = 0.0; + double max_err_allowed = this->max_error_ * this->spacing_; + //Loop exits when a position is found or when no more segments are available + while (true) + { + //Do not place markers too close to the beginning of a segment + if (spacing_left_ < this->marker_width_/2) + { + set_spacing_left(this->marker_width_/2); //Only moves forward + } + //Error for this marker is too large. Skip to the next position. + if (std::fabs(error_) > max_err_allowed) + { + while (this->error_ > this->spacing_) + { + error_ -= this->spacing_; //Avoid moving backwards + } + spacing_left_ += this->spacing_ - this->error_; + error_ = 0.0; + } + double dx = next_x - last_x; + double dy = next_y - last_y; + double segment_length = std::sqrt(dx * dx + dy * dy); + if (segment_length <= spacing_left_) + { + //Segment is too short to place marker. Find next segment + spacing_left_ -= segment_length; + last_x = next_x; + last_y = next_y; + while (agg::is_move_to(cmd = this->locator_.vertex(&next_x, &next_y))) + { + //Skip over "move" commands + last_x = next_x; + last_y = next_y; + } + if (agg::is_stop(cmd) || cmd == SEG_CLOSE) + { + this->done_ = true; + return false; + } + continue; //Try again + } + /* At this point we know the following things: + - segment_length > spacing_left + - error is small enough + - at least half a marker fits into this segment + */ + //Check if marker really fits in this segment + if (segment_length < this->marker_width_) + { + //Segment to short => Skip this segment + set_spacing_left(segment_length + this->marker_width_/2); //Only moves forward + continue; + } + else if (segment_length - spacing_left_ < this->marker_width_/2) + { + //Segment is long enough, but we are to close to the end + //Note: This function moves backwards. This could lead to an infinite + // loop when another function adds a positive offset. Therefore we + // only move backwards when there is no offset + if (error_ == 0) + { + set_spacing_left(segment_length - this->marker_width_/2, true); + } + else + { + //Skip this segment + set_spacing_left(segment_length + this->marker_width_/2); //Only moves forward + } + continue; //Force checking of max_error constraint + } + angle = std::atan2(dy, dx); + x = last_x + dx * (spacing_left_ / segment_length); + y = last_y + dy * (spacing_left_ / segment_length); + box2d box = this->perform_transform(angle, x, y); + if (!this->allow_overlap_ && !this->detector_.has_placement(box)) + { + //10.0 is the approxmiate number of positions tried and choosen arbitrarily + set_spacing_left(spacing_left_ + this->spacing_ * this->max_error_ / 10.0); //Only moves forward + continue; + } + if (!ignore_placement) + { + this->detector_.insert(box); + } + last_x = x; + last_y = y; + return true; + } + } + +private: + double last_x; + double last_y; + double next_x; + double 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 spacing_left_; + unsigned marker_nr_; + + /** Set spacing_left_, adjusts error_ and performs sanity checks. */ + void set_spacing_left(double sl, bool allow_negative=false) + { + double delta_error = sl - spacing_left_; + if (!allow_negative && delta_error < 0) + { + MAPNIK_LOG_WARN(markers_line_placement) + << "Unexpected negative error in markers_line_placement. " + "Please file a bug report."; + return; + } +#ifdef MAPNIK_DEBUG + if (delta_error == 0.0) + { + MAPNIK_LOG_WARN(markers_line_placement) + << "Not moving at all in set_spacing_left()! " + "Please file a bug report."; + } +#endif + error_ += delta_error; + spacing_left_ = sl; + } +}; + +} + +#endif // MAPNIK_MARKERS_PLACEMENTS_LINE_HPP diff --git a/include/mapnik/markers_placements/point.hpp b/include/mapnik/markers_placements/point.hpp new file mode 100644 index 000000000..b153a54e7 --- /dev/null +++ b/include/mapnik/markers_placements/point.hpp @@ -0,0 +1,157 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2014 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +#ifndef MAPNIK_MARKERS_PLACEMENTS_POINT_HPP +#define MAPNIK_MARKERS_PLACEMENTS_POINT_HPP + +#include +#include + +#include "agg_basics.h" +#include "agg_trans_affine.h" + +namespace mapnik { + +template +class markers_point_placement +{ +public: + markers_point_placement( + Locator &locator, + box2d const& size, + agg::trans_affine const& tr, + Detector &detector, + double spacing, + double max_error, + bool allow_overlap) + : locator_(locator), + size_(size), + tr_(tr), + detector_(detector), + spacing_(spacing), + max_error_(max_error), + allow_overlap_(allow_overlap), + marker_width_((size_ * tr_).width()), + done_(false) + { + rewind(); + } + + /** Start again at first marker. + * \note Returns the same list of markers only works when they were NOT added + * to the detector. + */ + virtual void rewind() + { + locator_.rewind(0); + done_ = false; + } + + /** 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 ignore_placement Whether to add selected position to detector + * \return True if a place is found, false if none is found. + */ + virtual bool get_point(double &x, double &y, double &angle, bool ignore_placement) + { + if (done_) + { + return false; + } + + if (locator_.type() == mapnik::geometry_type::types::LineString) + { + if (!label::middle_point(locator_, x, y)) + { + done_ = true; + return false; + } + } + else + { + if (!label::centroid(locator_, x, y)) + { + done_ = true; + return false; + } + } + + angle = 0; + + box2d box = perform_transform(angle, x, y); + + if (!allow_overlap_ && !detector_.has_placement(box)) + { + return false; + } + + if (!ignore_placement) + { + detector_.insert(box); + } + + done_ = true; + return true; + } + +protected: + Locator &locator_; + box2d size_; + agg::trans_affine tr_; + Detector &detector_; + double spacing_; + double max_error_; + bool allow_overlap_; + double marker_width_; + bool done_; + + /** Rotates the size_ box and translates the position. */ + box2d perform_transform(double angle, double dx, double dy) + { + double x1 = size_.minx(); + double x2 = size_.maxx(); + double y1 = size_.miny(); + double y2 = size_.maxy(); + agg::trans_affine tr = tr_ * agg::trans_affine_rotation(angle) + .translate(dx, dy); + double xA = x1, yA = y1, + xB = x2, yB = y1, + xC = x2, yC = y2, + xD = x1, yD = y2; + tr.transform(&xA, &yA); + tr.transform(&xB, &yB); + tr.transform(&xC, &yC); + tr.transform(&xD, &yD); + box2d result(xA, yA, xC, yC); + result.expand_to_include(xB, yB); + result.expand_to_include(xD, yD); + return result; + } +}; + +} + +#endif // MAPNIK_MARKERS_PLACEMENTS_POINT_HPP + diff --git a/src/cairo/cairo_renderer.cpp b/src/cairo/cairo_renderer.cpp index ff554c7f8..7920e0315 100644 --- a/src/cairo/cairo_renderer.cpp +++ b/src/cairo/cairo_renderer.cpp @@ -798,63 +798,37 @@ struct markers_dispatch : mapnik::noncopyable template void add_path(T & path) { - marker_placement_enum placement_method = get(sym_, keys::markers_placement_type, feature_, vars_, MARKER_POINT_PLACEMENT); - bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); - bool allow_overlap = get(sym_, keys::allow_overlap, feature_, vars_, false); - double opacity = get(sym_, keys::opacity, feature_, vars_, 1.0); - double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); - double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); - - if (placement_method != MARKER_LINE_PLACEMENT || - path.type() == geometry_type::types::Point) - { - double x = 0; - double y = 0; - if (path.type() == geometry_type::types::LineString) - { - if (!label::middle_point(path, x, y)) return; - } - else if (placement_method == MARKER_INTERIOR_PLACEMENT) - { - if (!label::interior_position(path, x, y)) return; - } - else - { - if (!label::centroid(path, x, y)) return; - } - coord2d center = bbox_.center(); - agg::trans_affine matrix = agg::trans_affine_translation(-center.x, -center.y); - matrix *= marker_trans_; - matrix *=agg::trans_affine_translation(x, y); - - box2d transformed_bbox = bbox_ * matrix; - - if (allow_overlap || - detector_.has_placement(transformed_bbox)) - { - render_vector_marker(ctx_, pixel_position(x,y), marker_, bbox_, attributes_, marker_trans_, opacity, true); - - if (!ignore_placement) - { - detector_.insert(transformed_bbox); - } - } - } - else - { - markers_placement placement(path, bbox_, marker_trans_, detector_, - spacing * scale_factor_, - max_error, - allow_overlap); - double x, y, angle; - while (placement.get_point(x, y, angle, ignore_placement)) - { - agg::trans_affine matrix = marker_trans_; - matrix.rotate(angle); - render_vector_marker(ctx_, pixel_position(x,y), marker_, bbox_, attributes_, matrix, opacity, true); - - } - } + marker_placement_enum placement_method = get( + sym_, keys::markers_placement_type, feature_, vars_, MARKER_POINT_PLACEMENT); + bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); + bool allow_overlap = get(sym_, keys::allow_overlap, feature_, vars_, false); + double opacity = get(sym_, keys::opacity, feature_, vars_, 1.0); + double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); + double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); + markers_placement_finder placement_finder( + placement_method, + path, + bbox_, + marker_trans_, + detector_, + spacing * scale_factor_, + max_error, + allow_overlap); + double x, y, angle = .0; + while (placement_finder.get_point(x, y, angle, ignore_placement)) + { + agg::trans_affine matrix = marker_trans_; + matrix.rotate(angle); + render_vector_marker( + ctx_, + pixel_position(x, y), + marker_, + bbox_, + attributes_, + matrix, + opacity, + true); + } } SvgPath & marker_; @@ -893,67 +867,32 @@ struct raster_markers_dispatch : mapnik::noncopyable template void add_path(T & path) { - marker_placement_enum placement_method = get(sym_, keys::markers_placement_type, feature_, vars_, MARKER_POINT_PLACEMENT); - double opacity = get(sym_, keys::opacity, feature_, vars_, 1.0); - double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); - double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); - bool allow_overlap = get(sym_, keys::allow_overlap, feature_, vars_, false); - bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); - - if (placement_method != MARKER_LINE_PLACEMENT || - path.type() == geometry_type::types::Point) - { - double x = 0; - double y = 0; - if (path.type() == geometry_type::types::LineString) - { - if (!label::middle_point(path, x, y)) - return; - } - else if (placement_method == MARKER_INTERIOR_PLACEMENT) - { - if (!label::interior_position(path, x, y)) - return; - } - else - { - if (!label::centroid(path, x, y)) - return; - } - coord2d center = bbox_.center(); - agg::trans_affine matrix = agg::trans_affine_translation(-center.x, -center.y); - matrix *= marker_trans_; - matrix *=agg::trans_affine_translation(x, y); - - box2d transformed_bbox = bbox_ * matrix; - - if (allow_overlap || - detector_.has_placement(transformed_bbox)) - { - ctx_.add_image(matrix, marker_, opacity); - if (!ignore_placement) - { - detector_.insert(transformed_bbox); - } - } - } - else - { - markers_placement placement(path, bbox_, marker_trans_, detector_, - spacing * scale_factor_, - max_error, - allow_overlap); - double x, y, angle; - while (placement.get_point(x, y, angle, ignore_placement)) - { - coord2d center = bbox_.center(); - agg::trans_affine matrix = agg::trans_affine_translation(-center.x, -center.y); - matrix *= marker_trans_; - matrix *= agg::trans_affine_rotation(angle); - matrix *= agg::trans_affine_translation(x, y); - ctx_.add_image(matrix, marker_, opacity); - } - } + marker_placement_enum placement_method = get(sym_, keys::markers_placement_type, feature_, vars_, MARKER_POINT_PLACEMENT); + double opacity = get(sym_, keys::opacity, feature_, vars_, 1.0); + double spacing = get(sym_, keys::spacing, feature_, vars_, 100.0); + double max_error = get(sym_, keys::max_error, feature_, vars_, 0.2); + bool allow_overlap = get(sym_, keys::allow_overlap, feature_, vars_, false); + bool ignore_placement = get(sym_, keys::ignore_placement, feature_, vars_, false); + markers_placement_finder placement_finder( + placement_method, + path, + bbox_, + marker_trans_, + detector_, + spacing * scale_factor_, + max_error, + allow_overlap); + double x, y, angle = .0; + while (placement_finder.get_point(x, y, angle, ignore_placement)) + { + coord2d center = bbox_.center(); + agg::trans_affine matrix = agg::trans_affine_translation( + -center.x, -center.y); + matrix *= marker_trans_; + matrix *= agg::trans_affine_rotation(angle); + matrix *= agg::trans_affine_translation(x, y); + ctx_.add_image(matrix, marker_, opacity); + } } ImageMarker & marker_;