From 89f6b32b76466626ee7171739ae0eb56e2fab822 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Sun, 30 May 2010 03:16:51 +0000 Subject: [PATCH] apply patch from Herm from #553 adding support for markers_symbolizer to Cairo renderer, new placement finder, and fill/spacing/overlap options - works nicely with new svg support being tested against markers_symbolizer --- include/mapnik/markers_converter.hpp | 166 ----------------- include/mapnik/markers_placement.hpp | 246 ++++++++++++++++++++++++++ include/mapnik/markers_symbolizer.hpp | 45 ++++- src/agg_renderer.cpp | 103 +++++++---- src/cairo_renderer.cpp | 37 +++- src/load_map.cpp | 108 +++++------ src/save_map.cpp | 29 ++- 7 files changed, 461 insertions(+), 273 deletions(-) create mode 100644 include/mapnik/markers_placement.hpp diff --git a/include/mapnik/markers_converter.hpp b/include/mapnik/markers_converter.hpp index 06b696ff5..e69de29bb 100644 --- a/include/mapnik/markers_converter.hpp +++ b/include/mapnik/markers_converter.hpp @@ -1,166 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2006 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 - * - *****************************************************************************/ - -//$Id$ - -#ifndef MARKERS_CONVERTER_HPP -#define MARKERS_CONVERTER_HPP - -#include "agg_basics.h" -#include "agg_trans_affine.h" -#include - -namespace mapnik { - template - class markers_converter : boost::noncopyable - { - public: - markers_converter(Locator & locator,Shape & shape, Detector & detector); - void rewind(unsigned path_id); - unsigned vertex(double * x, double * y); - private: - enum status_e - { - initial, - markers, - polygon, - stop - }; - - Locator & locator_; - Shape & shape_; - Detector & detector_; - status_e status_; - agg::trans_affine transform_; - agg::trans_affine mtx_; - unsigned num_markers_; - unsigned marker_; - }; - - template - markers_converter::markers_converter(Locator & locator,Shape & shape, - Detector & detector) - : locator_(locator), - shape_(shape), - detector_(detector), - status_(initial), - num_markers_(1), - marker_(0) {} - - template - void markers_converter::rewind(unsigned path_id) - { - status_ = initial; - marker_ = 0; - num_markers_ = 1; - } - - template - unsigned markers_converter::vertex( double * x, double * y) - { - unsigned cmd = agg::path_cmd_move_to; - - double x1, y1, x2, y2; - while (!agg::is_stop(cmd)) - { - switch (status_) - { - case initial: - if (num_markers_ == 0 ) - { - cmd = agg::path_cmd_stop; - break; - } - locator_.rewind(marker_++); - status_ = markers; - num_markers_ = 0; - break; - case markers: - { - unsigned cmd1; - while (agg::is_move_to(cmd1 = locator_.vertex(&x1,&y1))); - if (agg::is_stop(cmd1)) - { - status_ = stop; - break; - } - unsigned cmd2 = locator_.vertex(&x2,&y2); - if (agg::is_stop(cmd2)) - { - status_ = stop; - break; - } - - - ++num_markers_; - double dx = x2 - x1; - double dy = y2 - y1; - double d = std::sqrt(dx * dx + dy * dy); - box2d ext = shape_.extent(); - if (d > ext.width()) - { - mtx_ = transform_; - mtx_ *= agg::trans_affine_rotation(::atan2(dy,dx)); - double marker_x = x1 + dx * 0.5; - double marker_y = y1 + dy * 0.5; - - mtx_ *= agg::trans_affine_translation(marker_x,marker_y); - - double minx = ext.minx(); - double miny = ext.miny(); - double maxx = ext.maxx(); - double maxy = ext.maxy(); - mtx_.transform(&minx,&miny); - mtx_.transform(&maxx,&maxy); - - box2d e0(minx,miny,maxx,maxy); - - if (detector_.has_placement(e0)) - { - shape_.rewind(0); - status_ = polygon; - detector_.insert(ext); - } - } - - break; - } - case polygon: - cmd = shape_.vertex(x,y); - if (agg::is_stop(cmd)) - { - cmd = agg::path_cmd_move_to; - status_ = markers; - break; - } - mtx_.transform(x,y); - return cmd; - case stop: - cmd = agg::path_cmd_stop; - break; - } - } - return cmd; - } -} - -#endif // MARKERS_CONVERTER_HPP diff --git a/include/mapnik/markers_placement.hpp b/include/mapnik/markers_placement.hpp new file mode 100644 index 000000000..fa454ec54 --- /dev/null +++ b/include/mapnik/markers_placement.hpp @@ -0,0 +1,246 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2010 Hermann Kraus + * + * 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 MARKERS_PLACEMENT_HPP +#define MARKERS_PLACEMENT_HPP + +#include "agg_basics.h" +#include + +#include +#include + +namespace mapnik { + +template +class markers_placement : boost::noncopyable +{ + public: + markers_placement(Locator &locator, box2d size, Detector &detector, double spacing, double max_error, bool allow_overlap); + void rewind(); + bool get_point(double *x, double *y, double *angle, bool add_to_detector = true); + private: + Locator &locator_; + box2d size_; + Detector &detector_; + double spacing_; + 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 markers distance will be a bit lower. */ + double error_; + double max_error_; + unsigned marker_nr_; + bool allow_overlap_; + box2d perform_transform(double angle, double dx, double dy); + double find_optimal_spacing(double s); +}; + +/** 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( + 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) +{ + if (spacing >= 0) + { + spacing_ = spacing; + } else if (spacing < 0) + { + spacing_ = find_optimal_spacing(-spacing); + } + rewind(); +} + +/** Autmatically chooses spacing. */ +template double markers_placement::find_optimal_spacing(double s) +{ + rewind(); + //Calculate total path length + unsigned cmd = agg::path_cmd_move_to; + double length = 0; + while (!agg::is_stop(cmd)) + { + double dx = next_x - last_x; + double dy = next_y - last_y; + length += std::sqrt(dx * dx + dy * dy); + 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; + } + } + unsigned points = round(length / s); + if (points == 0) return 0; //Path to short + return length / points; +} + +/** Start again at first marker. + * \note Returning the same list of markers only works when they were NOT added + * to the detector. + */ +template void markers_placement::rewind() +{ + locator_.rewind(0); + //Get first point + done_ = agg::is_stop(locator_.vertex(&next_x, &next_y)) || spacing_ < size_.width(); + last_x = next_x; + last_y = next_y; // Force request of new segment + error_ = 0; + marker_nr_ = 0; +} + +/** 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. + */ +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; + if (marker_nr_++ == 0) + { + spacing_left = spacing_ / 2; + } else + { + spacing_left = spacing_; + } + + spacing_left -= error_; + error_ = 0; + + 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 to close to the beginning of a segment + error_ += size_.width()/2 - spacing_left; + spacing_left = size_.width()/2; + } + if (abs(error_) > max_error_ * spacing_) + { + 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) + { + //Segment is to short to place marker. Find next segment + spacing_left -= d; + 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)) + { + done_ = true; + return false; + } + continue; //Try again + } + //Check if marker really fits in this segment + if (d < size_.width()) + { + //Segment to short => Skip this segment + error_ += d + size_.width()/2 - spacing_left; + spacing_left = d + size_.width()/2; + continue; + } else if (d - spacing_left < size_.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) + { + error_ += d - size_.width()/2 - spacing_left; + spacing_left = d - size_.width()/2; + } else + { + //Skip this segment + error_ += d + size_.width()/2 - spacing_left; + spacing_left = d + size_.width()/2; + } + continue; //Force checking of max_error constraint + } + *angle = atan2(dy, dx); + *x = last_x + dx * (spacing_left / d); + *y = last_y + dy * (spacing_left / d); + + box2d 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; + continue; + } + if (add_to_detector) detector_.insert(box); + last_x = *x; + last_y = *y; + return true; + } +} + +/** Rotates the size_ box and translates the position. */ +template box2d markers_placement::perform_transform(double angle, double dx, double dy) +{ + double c = cos(angle), s = sin(angle); + double x1 = size_.minx(); + double x2 = size_.maxx(); + double y1 = size_.miny(); + double y2 = size_.maxy(); + + double x1_ = dx + x1 * c - y1 * s; + double y1_ = dy + x1 * s + y1 * c; + double x2_ = dx + x2 * c - y2 * s; + double y2_ = dy + x2 * s + y2 * c; + + return box2d(x1_, y1_, x2_, y2_); +} +}; /* end namespace */ +#endif // MARKERS_PLACEMENT_HPP diff --git a/include/mapnik/markers_symbolizer.hpp b/include/mapnik/markers_symbolizer.hpp index 7adcf389b..b693d09d7 100644 --- a/include/mapnik/markers_symbolizer.hpp +++ b/include/mapnik/markers_symbolizer.hpp @@ -27,6 +27,7 @@ //mapnik #include +#include namespace mapnik { @@ -34,17 +35,17 @@ struct MAPNIK_DECL markers_symbolizer { public: markers_symbolizer(path_expression_ptr filename, bool allow_overlap=false) - : filename_(filename), - allow_overlap_(allow_overlap) {} + : filename_(filename), allow_overlap_(allow_overlap), + fill_(color(0,0,255)), spacing_(100.0), max_error_(0.2) {} path_expression_ptr get_filename() const { - return filename_; + return filename_; } void set_filename(path_expression_ptr filename) { - filename_ = filename; + filename_ = filename; } void set_allow_overlap(bool overlap) @@ -52,13 +53,47 @@ public: allow_overlap_ = overlap; } - float get_allow_overlap() const + bool get_allow_overlap() const { return allow_overlap_; } + + void set_spacing(double spacing) + { + spacing_ = spacing; + } + + float get_spacing() const + { + return spacing_; + } + + void set_max_error(double max_error) + { + max_error_ = max_error; + } + + float get_max_error() const + { + return max_error_; + } + + void set_fill(color fill) + { + fill_ = fill; + } + + color const& get_fill() const + { + return fill_; + } + private: path_expression_ptr filename_; bool allow_overlap_; + color fill_; + double spacing_; + double max_error_; }; } diff --git a/src/agg_renderer.cpp b/src/agg_renderer.cpp index 4c4404c66..541553578 100644 --- a/src/agg_renderer.cpp +++ b/src/agg_renderer.cpp @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include @@ -816,47 +816,76 @@ template void agg_renderer::process(markers_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) -{ +{ typedef coord_transform2 path_type; typedef agg::pixfmt_rgba32 pixfmt; typedef agg::renderer_base renderer_base; typedef agg::renderer_scanline_aa_solid renderer_solid; - - std::string filename = path_processor_type::evaluate( *sym.get_filename(), feature); - - if (!filename.empty()) + + bool svg_marker; + arrow arrow_; + box2d extent; + boost::optional marker; + + ras_ptr->reset(); + ras_ptr->gamma(agg::gamma_linear()); + agg::scanline_u8 sl; + agg::rendering_buffer buf(pixmap_.raw_data(), width_, height_, width_ * 4); + pixfmt pixf(buf); + renderer_base renb(pixf); + renderer_solid ren(renb); + + color const& fill_ = sym.get_fill(); + unsigned r = fill_.red(); + unsigned g = fill_.green(); + unsigned b = fill_.blue(); + unsigned a = fill_.alpha(); + + + for (unsigned i=0; i marker = mapnik::marker_cache::instance()->find(filename,true); - - if (marker && *marker) - { - ras_ptr->reset(); - ras_ptr->gamma(agg::gamma_linear()); - agg::scanline_u8 sl; - agg::rendering_buffer buf(pixmap_.raw_data(),width_,height_, width_ * 4); - pixfmt pixf(buf); - renderer_base renb(pixf); - renderer_solid ren(renb); - - //mtx *= agg::trans_affine_translation((lox + hix) * -0.5, (loy + hiy) * -0.5); - //mtx *= agg::trans_affine_scaling(0.8 * std::min(im.width()/(hix-lox),im.height()/(hiy-loy))); - //mtx *= agg::trans_affine_translation((lox + hix) * 0.5, (loy + hiy) * 0.5); - //mtx *= agg::trans_affine_translation((im.width() - (lox + hix)) * 0.5, (im.height() - (loy + hiy)) * 0.5); - - for (unsigned i=0;irender(*ras_ptr, sl, ren, mtx, renb.clip_box(), 1.0); - } - } + geometry2d const& geom = feature.get_geometry(i); + if (geom.num_points() <= 1) continue; + + std::string filename = path_processor_type::evaluate(*sym.get_filename(), feature); + + if (!filename.empty()) + { + marker = mapnik::marker_cache::instance()->find(filename, true); + if (marker && *marker) + { + svg_marker = true; + double x1, y1, x2, y2; + (*marker)->bounding_rect(&x1, &y1, &x2, &y2); + extent.init(x1, y1, x2, y2); + } + } + else + { + svg_marker = false; + extent = arrow_.extent(); + } + + path_type path(t_,geom,prj_trans); + markers_placement placement(path, extent, detector_, sym.get_spacing(), sym.get_max_error(), sym.get_allow_overlap()); + + double x, y, angle; + + while (placement.get_point(&x, &y, &angle)) + { + agg::trans_affine matrix = agg::trans_affine_rotation(angle) * agg::trans_affine_translation(x, y); + if (svg_marker) + { + (*marker)->render(*ras_ptr, sl, ren, matrix, renb.clip_box(), 1.0); + } + else + { + agg::conv_transform trans(arrow_, matrix); + ras_ptr->add_path(trans); + ren.color(agg::rgba8(r, g, b, a)); + agg::render_scanlines(*ras_ptr, sl, ren); + } + } } } diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index b12b7cd28..fcac8a23d 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include @@ -291,7 +291,7 @@ public: } template - void add_path(T path) + void add_path(T& path) { double x, y; @@ -357,6 +357,11 @@ public: context_->set_font_matrix(matrix); } + void set_matrix(Cairo::Matrix const& matrix) + { + context_->set_matrix(matrix); + } + void show_glyph(unsigned long index, double x, double y) { Cairo::Glyph glyph; @@ -948,7 +953,7 @@ void cairo_renderer_base::process(line_pattern_symbolizer const& sym, double dy = y - y0; double angle = atan2(dy, dx); double offset = fmod(length, width); - + Cairo::Matrix matrix; cairo_matrix_init_identity(&matrix); cairo_matrix_translate(&matrix,x0,y0); @@ -1055,6 +1060,32 @@ void cairo_renderer_base::process(markers_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { + typedef coord_transform2 path_type; + arrow arrow_; + cairo_context context(context_); + + color const& fill_ = sym.get_fill(); + context.set_color(fill_.red(), fill_.green(), fill_.blue(), fill_.alpha()); + + for (unsigned i = 0; i < feature.num_geometries(); ++i) + { + geometry2d const& geom = feature.get_geometry(i); + + if (geom.num_points() > 1) + { + path_type path(t_, geom, prj_trans); + + markers_placement placement(path, arrow_.extent(), detector_, sym.get_spacing(), sym.get_max_error(), sym.get_allow_overlap()); + + double x, y, angle; + while (placement.get_point(&x, &y, &angle)) { + Cairo::Matrix matrix = Cairo::rotation_matrix(angle) * Cairo::translation_matrix(x,y) ; + context.set_matrix(matrix); + context.add_path(arrow_); + } + } + context.fill(); + } } void cairo_renderer_base::process(glyph_symbolizer const& sym, diff --git a/src/load_map.cpp b/src/load_map.cpp index 2cfebd7b8..95b5e752f 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -746,67 +746,67 @@ void map_parser::parse_markers_symbolizer( rule_type & rule, ptree const & sym ) { try { - optional file = get_opt_attr(sym, "file"); - optional base = get_opt_attr(sym, "base"); - optional allow_overlap = - get_opt_attr(sym, "allow_overlap"); - optional opacity = - get_opt_attr(sym, "opacity"); - - if (file) - { - try - { - if( base ) - { - std::map::const_iterator itr = file_sources_.find(*base); - if (itr!=file_sources_.end()) - { - *file = itr->second + "/" + *file; - } - } + std::string filename(""); + optional file = get_opt_attr(sym, "file"); + optional base = get_opt_attr(sym, "base"); - if ( relative_to_xml_ ) - { - *file = ensure_relative_to_xml(file); - } + if (file) + { + try + { + if (base) + { + std::map::const_iterator itr = file_sources_.find(*base); + if (itr!=file_sources_.end()) + { + *file = itr->second + "/" + *file; + } + } + + if ( relative_to_xml_ ) + { + *file = ensure_relative_to_xml(file); + } #ifdef MAPNIK_DEBUG - else { - std::clog << "\nFound relative paths in xml, leaving unchanged...\n"; - } + else { + std::clog << "\nFound relative paths in xml, leaving unchanged...\n"; + } #endif - - markers_symbolizer symbol(parse_path(*file)); - - if (allow_overlap) - { - symbol.set_allow_overlap( * allow_overlap ); - } - if (opacity) - { - // TODO !!!!! symbol.set_opacity( *opacity ); - } - rule.append(symbol); - } - catch (...) - { - string msg("Failed to load marker file '" + * file); - if (strict_) - { - throw config_error(msg); - } - else - { - clog << "### WARNING: " << msg << endl; - } - } - + filename = *file; + } + catch (...) + { + string msg("Failed to load marker file '" + *file + "'!"); + if (strict_) + { + throw config_error(msg); + } + else + { + clog << "### WARNING: " << msg << endl; + } + } } + + markers_symbolizer symbol(parse_path(filename)); + optional opacity = get_opt_attr(sym, "opacity"); + if (opacity) { + // TODO !!!!! symbol.set_opacity( *opacity ); + } + optional c = get_opt_attr(sym, "fill"); + if (c) symbol.set_fill(*c); + optional spacing = get_opt_attr(sym, "spacing"); + if (spacing) symbol.set_spacing(*spacing); + optional max_error = get_opt_attr(sym, "max_error"); + if (max_error) symbol.set_max_error(*max_error); + optional allow_overlap = get_opt_attr(sym, "allow_overlap"); + if (allow_overlap) symbol.set_allow_overlap(*allow_overlap); + rule.append(symbol); } catch (const config_error & ex) { - ex.append_context("in MarkersSymbolizer"); - throw; + ex.append_context("in MarkersSymbolizer"); + throw; } } diff --git a/src/save_map.cpp b/src/save_map.cpp index fa2498aed..dd471c83f 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -262,14 +262,27 @@ public: { ptree & sym_node = rule_.push_back( ptree::value_type("MarkersSymbolizer", ptree()))->second; - const std::string & filename = path_processor_type::to_string( *sym.get_filename()); - // TODO!!! -//markers_symbolizer dfl - //if ( sym.get_allow_overlap() != dfl.get_allow_overlap() || explicit_defaults_ ) - //{ - // set_attr( sym_node, "allow_overlap", sym.get_allow_overlap() ); - //} - + markers_symbolizer dfl(parse_path("")); //TODO: Parameter? + std::string const& filename = path_processor_type::to_string( *sym.get_filename()); + if ( ! filename.empty() ) { + set_attr( sym_node, "file", filename ); + } + if (sym.get_allow_overlap() != dfl.get_allow_overlap() || explicit_defaults_) + { + set_attr( sym_node, "allow_overlap", sym.get_allow_overlap() ); + } + if (sym.get_spacing() != dfl.get_spacing() || explicit_defaults_) + { + set_attr( sym_node, "spacing", sym.get_spacing() ); + } + if (sym.get_max_error() != dfl.get_max_error() || explicit_defaults_) + { + set_attr( sym_node, "max_error", sym.get_max_error() ); + } + if (sym.get_fill() != dfl.get_fill() || explicit_defaults_) + { + set_attr( sym_node, "fill", sym.get_fill() ); + } } void operator () ( glyph_symbolizer const& sym)