diff --git a/CHANGELOG b/CHANGELOG index c4f0446fb..0be23c0f6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,11 @@ For a complete change history, see the SVN log. Mapnik 2.0.1 ------------ +- Cairo: Add full rendering support for markers to match AGG renderer functionality (#1071) + +- Fix Markers rendering so that ellipse height/width units are pixels (previously were unintentially radii) (#1134) + +- Added 'ignore-placement` attribute to markers-symbolizer (#1135) - Switched back to "libmapnik" and "import mapnik" rather than "mapnik2" (#941) diff --git a/bindings/python/mapnik_markers_symbolizer.cpp b/bindings/python/mapnik_markers_symbolizer.cpp index 71d76c2f3..e8dc3dad6 100644 --- a/bindings/python/mapnik_markers_symbolizer.cpp +++ b/bindings/python/mapnik_markers_symbolizer.cpp @@ -60,7 +60,8 @@ struct markers_symbolizer_pickle_suite : boost::python::pickle_suite static boost::python::tuple getstate(markers_symbolizer const& p) { - return boost::python::make_tuple(p.get_allow_overlap());//,p.get_opacity()); + return boost::python::make_tuple(p.get_allow_overlap(), + p.get_ignore_placement());//,p.get_opacity()); } static void @@ -77,8 +78,7 @@ struct markers_symbolizer_pickle_suite : boost::python::pickle_suite } p.set_allow_overlap(extract(state[0])); - //p.set_opacity(extract(state[1])); - + p.set_ignore_placement(extract(state[1])); } }; @@ -108,8 +108,19 @@ void export_markers_symbolizer() &markers_symbolizer::get_opacity, &markers_symbolizer::set_opacity, "Set/get the text opacity") + .add_property("ignore_placement", + &markers_symbolizer::get_ignore_placement, + &markers_symbolizer::set_ignore_placement) .add_property("transform", &mapnik::get_svg_transform, &mapnik::set_svg_transform) + .add_property("width", + &markers_symbolizer::get_width, + &markers_symbolizer::set_width, + "Set/get the marker width") + .add_property("height", + &markers_symbolizer::get_height, + &markers_symbolizer::set_height, + "Set/get the marker height") ; } diff --git a/include/mapnik/cairo_renderer.hpp b/include/mapnik/cairo_renderer.hpp index 60a79b144..52ae8a789 100644 --- a/include/mapnik/cairo_renderer.hpp +++ b/include/mapnik/cairo_renderer.hpp @@ -131,7 +131,7 @@ public: } protected: - void render_marker(const int x, const int y, marker &marker, const agg::trans_affine & mtx, double opacity=1.0); + void render_marker(const int x, const int y, marker &marker, const agg::trans_affine & mtx, double opacity=1.0, bool recenter=true); Map const& m_; Cairo::RefPtr context_; diff --git a/include/mapnik/markers_symbolizer.hpp b/include/mapnik/markers_symbolizer.hpp index 507519157..c0211df78 100644 --- a/include/mapnik/markers_symbolizer.hpp +++ b/include/mapnik/markers_symbolizer.hpp @@ -58,6 +58,8 @@ public: explicit markers_symbolizer(); markers_symbolizer(path_expression_ptr filename); markers_symbolizer(markers_symbolizer const& rhs); + void set_ignore_placement(bool ignore_placement); + bool get_ignore_placement() const; void set_allow_overlap(bool overlap); bool get_allow_overlap() const; void set_spacing(double spacing); @@ -78,6 +80,7 @@ public: marker_type_e get_marker_type() const; private: + bool ignore_placement_; bool allow_overlap_; color fill_; double spacing_; diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index d40b02ee7..f69050f82 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -142,7 +142,9 @@ void agg_renderer::process(markers_symbolizer const& sym, unsigned s_a=col.alpha(); double w = sym.get_width(); double h = sym.get_height(); - + double rx = w/2.0; + double ry = h/2.0; + arrow arrow_; box2d extent; @@ -196,7 +198,7 @@ void agg_renderer::process(markers_symbolizer const& sym, if (sym.get_allow_overlap() || detector_.has_placement(label_ext)) { - agg::ellipse c(x, y, w, h); + agg::ellipse c(x, y, rx, ry); marker.concat_path(c); ras_ptr->add_path(marker); ren.color(agg::rgba8(r, g, b, int(a*sym.get_opacity()))); @@ -215,7 +217,8 @@ void agg_renderer::process(markers_symbolizer const& sym, ren.color(agg::rgba8(s_r, s_g, s_b, int(s_a*stroke_.get_opacity()))); agg::render_scanlines(*ras_ptr, sl_line, ren); } - detector_.insert(label_ext); + if (!sym.get_ignore_placement()) + detector_.insert(label_ext); if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second); } } @@ -239,7 +242,7 @@ void agg_renderer::process(markers_symbolizer const& sym, if (marker_type == ELLIPSE) { // todo proper bbox - this is buggy - agg::ellipse c(x_t, y_t, w, h); + agg::ellipse c(x_t, y_t, rx, ry); marker.concat_path(c); agg::trans_affine matrix; matrix *= agg::trans_affine_translation(-x_t,-y_t); diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 3f69f38d5..d94e1d626 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -40,6 +40,9 @@ #include #include +#include "agg_path_storage.h" // for markers_symbolizer +#include "agg_ellipse.h" // for markers_symbolizer + // cairo #include #include @@ -908,7 +911,7 @@ void cairo_renderer_base::process(line_symbolizer const& sym, } } -void cairo_renderer_base::render_marker(const int x, const int y, marker &marker, const agg::trans_affine & tr, double opacity) +void cairo_renderer_base::render_marker(const int x, const int y, marker &marker, const agg::trans_affine & tr, double opacity, bool recenter) { cairo_context context(context_); @@ -917,15 +920,21 @@ void cairo_renderer_base::render_marker(const int x, const int y, marker &marker box2d bbox; bbox = (*marker.get_vector_data())->bounding_box(); - coord c = bbox.center(); - // center the svg marker on '0,0' - agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y); - // apply symbol transformation to get to map space - mtx *= tr; - // render the marker at the center of the marker box - mtx.translate(x+0.5 * marker.width(), y+0.5 * marker.height()); + agg::trans_affine mtx = tr; + + if (recenter) + { + coord c = bbox.center(); + // center the svg marker on '0,0' + mtx = agg::trans_affine_translation(-c.x,-c.y); + // apply symbol transformation to get to map space + mtx *= tr; + // render the marker at the center of the marker box + mtx.translate(x+0.5 * marker.width(), y+0.5 * marker.height()); + } typedef coord_transform2 path_type; + agg::trans_affine transform; mapnik::path_ptr vmarker = *marker.get_vector_data(); agg::pod_bvector const & attributes_ = vmarker->attributes(); @@ -937,10 +946,10 @@ void cairo_renderer_base::render_marker(const int x, const int y, marker &marker context.save(); - agg::trans_affine transform = attr.transform; + transform = attr.transform; transform *= mtx; - if (transform.is_valid() && !transform.is_identity()) + if (/*transform.is_valid() &&*/ !transform.is_identity()) { double m[6]; transform.store_to(m); @@ -1423,34 +1432,236 @@ 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()); + double scale_factor_(1); - for (unsigned i = 0; i < feature.num_geometries(); ++i) + typedef coord_transform2 path_type; + + agg::trans_affine tr; + boost::array const& m = sym.get_transform(); + tr.load_from(&m[0]); + // TODO - use this? + //tr = agg::trans_affine_scaling(scale_factor_) * tr; + std::string filename = path_processor_type::evaluate(*sym.get_filename(), feature); + marker_placement_e placement_method = sym.get_marker_placement(); + marker_type_e marker_type = sym.get_marker_type(); + metawriter_with_properties writer = sym.get_metawriter(); + + if (!filename.empty()) { - geometry_type const& geom = feature.get_geometry(i); - - if (geom.num_points() > 1) + boost::optional mark = mapnik::marker_cache::instance()->find(filename, true); + if (mark && *mark) { - path_type path(t_, geom, prj_trans); + if (!(*mark)->is_vector()) { + std::clog << "### Warning only svg markers are supported in the markers_symbolizer\n"; + return; + } + boost::optional marker = (*mark)->get_vector_data(); + box2d const& bbox = (*marker)->bounding_box(); + double x1 = bbox.minx(); + double y1 = bbox.miny(); + double x2 = bbox.maxx(); + double y2 = bbox.maxy(); + double w = (*mark)->width(); + double h = (*mark)->height(); - markers_placement placement(path, arrow_.extent(), detector_, sym.get_spacing(), sym.get_max_error(), sym.get_allow_overlap()); + agg::trans_affine recenter = agg::trans_affine_translation(-0.5*(x1+x2),-0.5*(y1+y2)); + tr.transform(&x1,&y1); + tr.transform(&x2,&y2); + box2d extent(x1,y1,x2,y2); + using namespace mapnik::svg; - 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_); + for (unsigned i=0; iadd_box(extent, feature, t_, writer.second); + } + } + else + { + path_type path(t_,geom,prj_trans); + markers_placement placement(path, extent, detector_, + sym.get_spacing() * scale_factor_, + sym.get_max_error(), + sym.get_allow_overlap()); + double x, y, angle; + + while (placement.get_point(&x, &y, &angle)) + { + agg::trans_affine matrix = recenter * tr * agg::trans_affine_rotation(angle) * agg::trans_affine_translation(x, y); + render_marker(x - 0.5 * w, y - 0.5 * h, **mark, matrix, sym.get_opacity(),false); + + if (writer.first) + //writer.first->add_box(label_ext, feature, t_, writer.second); + std::clog << "### Warning metawriter not yet supported for LINE placement\n"; + } + } + context.fill(); + } + } + } + else + { + color const& fill_ = sym.get_fill(); + stroke const& stroke_ = sym.get_stroke(); + color const& col = stroke_.get_color(); + double strk_width = stroke_.get_width(); + double w = sym.get_width(); + double h = sym.get_height(); + double rx = w/2.0; + double ry = h/2.0; + + arrow arrow_; + box2d extent; + + double dx = w + (2*strk_width); + double dy = h + (2*strk_width); + + if (marker_type == ARROW) + { + extent = arrow_.extent(); + double x1 = extent.minx(); + double y1 = extent.miny(); + double x2 = extent.maxx(); + double y2 = extent.maxy(); + tr.transform(&x1,&y1); + tr.transform(&x2,&y2); + extent.init(x1,y1,x2,y2); + } + else + { + double x1 = -1 *(dx); + double y1 = -1 *(dy); + double x2 = dx; + double y2 = dy; + tr.transform(&x1,&y1); + tr.transform(&x2,&y2); + extent.init(x1,y1,x2,y2); + } + + double x; + double y; + double z=0; + + agg::path_storage marker; + + for (unsigned i=0; i label_ext (px, py, px + dx +1, py + dy +1); + if (sym.get_allow_overlap() || + detector_.has_placement(label_ext)) + { + agg::ellipse c(x, y, rx, ry); + marker.concat_path(c); + context.set_color(fill_,sym.get_opacity()); + context.add_agg_path(marker); + context.fill(); + + if (strk_width) + { + //context.restore(); + context.set_color(col,stroke_.get_opacity()); + context.set_line_width(stroke_.get_width()); + if (stroke_.has_dash()) + { + context.set_dash(stroke_.get_dash_array()); + } + context.add_agg_path(marker); + context.stroke(); + } + if (!sym.get_ignore_placement()) + detector_.insert(label_ext); + if (writer.first) writer.first->add_box(label_ext, feature, t_, writer.second); + } + } + else + { + if (marker_type == ARROW) + marker.concat_path(arrow_); + + path_type path(t_,geom,prj_trans); + markers_placement placement(path, extent, detector_, + sym.get_spacing() * scale_factor_, + sym.get_max_error(), + sym.get_allow_overlap()); + double x_t, y_t, angle; + + while (placement.get_point(&x_t, &y_t, &angle)) + { + agg::trans_affine matrix; + + if (marker_type == ELLIPSE) + { + agg::ellipse c(x_t, y_t, rx, ry); + marker.concat_path(c); + agg::trans_affine matrix; + matrix *= agg::trans_affine_translation(-x_t,-y_t); + matrix *= agg::trans_affine_rotation(angle); + matrix *= agg::trans_affine_translation(x_t,y_t); + marker.transform(matrix); + } + else + { + matrix = tr * agg::trans_affine_rotation(angle) * agg::trans_affine_translation(x_t, y_t); + } + + // TODO + if (writer.first) + //writer.first->add_box(label_ext, feature, t_, writer.second); + std::clog << "### Warning metawriter not yet supported for LINE placement\n"; + + agg::conv_transform trans(marker, matrix); + context.set_color(fill_,sym.get_opacity()); + context.add_agg_path(trans); + context.fill(); + + if (strk_width) + { + context.set_color(col,stroke_.get_opacity()); + context.set_line_width(stroke_.get_width()); + if (stroke_.has_dash()) + { + context.set_dash(stroke_.get_dash_array()); + } + context.add_agg_path(trans); + context.stroke(); + } + } } } - context.fill(); } } + void cairo_renderer_base::process(glyph_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) diff --git a/src/grid/process_markers_symbolizer.cpp b/src/grid/process_markers_symbolizer.cpp index 84b2da613..043ee814b 100644 --- a/src/grid/process_markers_symbolizer.cpp +++ b/src/grid/process_markers_symbolizer.cpp @@ -145,7 +145,10 @@ void grid_renderer::process(markers_symbolizer const& sym, w = sym.get_width()/res; h = sym.get_height()/res; } - + + double rx = w/2.0; + double ry = h/2.0; + arrow arrow_; box2d extent; @@ -193,7 +196,7 @@ void grid_renderer::process(markers_symbolizer const& sym, if (sym.get_allow_overlap() || detector_.has_placement(label_ext)) { - agg::ellipse c(x, y, w, h); + agg::ellipse c(x, y, rx, ry); agg::path_storage marker; marker.concat_path(c); ras_ptr->add_path(marker); @@ -230,7 +233,7 @@ void grid_renderer::process(markers_symbolizer const& sym, if (marker_type == ELLIPSE) { // todo proper bbox - this is buggy - agg::ellipse c(x_t, y_t, w, h); + agg::ellipse c(x_t, y_t, rx, ry); marker.concat_path(c); agg::trans_affine matrix; matrix *= agg::trans_affine_translation(-x_t,-y_t); diff --git a/src/load_map.cpp b/src/load_map.cpp index d89110563..13c6a76d2 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1047,7 +1047,9 @@ void map_parser::parse_markers_symbolizer( rule & rule, ptree const & sym ) 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"); + optional ignore_placement = get_opt_attr(sym, "ignore-placement"); if (allow_overlap) symbol.set_allow_overlap(*allow_overlap); + if (ignore_placement) symbol.set_ignore_placement(*ignore_placement); optional w = get_opt_attr(sym, "width"); optional h = get_opt_attr(sym, "height"); diff --git a/src/markers_symbolizer.cpp b/src/markers_symbolizer.cpp index 7588d66e9..d28d3d6ab 100644 --- a/src/markers_symbolizer.cpp +++ b/src/markers_symbolizer.cpp @@ -47,11 +47,12 @@ markers_symbolizer::markers_symbolizer() : symbolizer_with_image(path_expression_ptr(new path_expression)), symbolizer_base(), allow_overlap_(false), + ignore_placement_(false), fill_(color(0,0,255)), spacing_(100.0), max_error_(0.2), - width_(5.0), - height_(5.0), + width_(10.0), + height_(10.0), stroke_(), marker_p_(MARKER_LINE_PLACEMENT), marker_type_(ARROW) {} @@ -60,11 +61,12 @@ markers_symbolizer::markers_symbolizer(path_expression_ptr filename) : symbolizer_with_image(filename), symbolizer_base(), allow_overlap_(false), + ignore_placement_(false), fill_(color(0,0,255)), spacing_(100.0), max_error_(0.2), - width_(5.0), - height_(5.0), + width_(10.0), + height_(10.0), stroke_(), marker_p_(MARKER_LINE_PLACEMENT), marker_type_(ARROW) {} @@ -73,15 +75,26 @@ markers_symbolizer::markers_symbolizer(markers_symbolizer const& rhs) : symbolizer_with_image(rhs), symbolizer_base(rhs), allow_overlap_(rhs.allow_overlap_), - fill_(rhs.fill_), - spacing_(rhs.spacing_), + ignore_placement_(rhs.ignore_placement_), + fill_(rhs.fill_), + spacing_(rhs.spacing_), max_error_(rhs.max_error_), width_(rhs.width_), height_(rhs.height_), stroke_(rhs.stroke_), marker_p_(rhs.marker_p_), marker_type_(rhs.marker_type_) {} - + +void markers_symbolizer::set_ignore_placement(bool ignore_placement) +{ + ignore_placement_ = ignore_placement; +} + +bool markers_symbolizer::get_ignore_placement() const +{ + return ignore_placement_; +} + void markers_symbolizer::set_allow_overlap(bool overlap) { allow_overlap_ = overlap; diff --git a/src/save_map.cpp b/src/save_map.cpp index 0e885c9ce..2c7a12be7 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -261,6 +261,10 @@ public: { set_attr( sym_node, "allow-overlap", sym.get_allow_overlap() ); } + if (sym.get_ignore_placement() != dfl.get_ignore_placement() || explicit_defaults_) + { + set_attr( sym_node, "ignore-placement", sym.get_ignore_placement() ); + } if (sym.get_spacing() != dfl.get_spacing() || explicit_defaults_) { set_attr( sym_node, "spacing", sym.get_spacing() ); diff --git a/tests/python_tests/render_grid_test.py b/tests/python_tests/render_grid_test.py index 860c9b171..0f252f482 100644 --- a/tests/python_tests/render_grid_test.py +++ b/tests/python_tests/render_grid_test.py @@ -44,6 +44,8 @@ def create_grid_map(width,height): r = mapnik.Rule() #symb = mapnik.PointSymbolizer() symb = mapnik.MarkersSymbolizer() + symb.width = 10 + symb.height = 10 symb.allow_overlap = True r.symbols.append(symb) label = mapnik.TextSymbolizer(mapnik.Expression('[Name]'), diff --git a/tests/python_tests/render_test.py b/tests/python_tests/render_test.py index 409352fba..abad9cea8 100644 --- a/tests/python_tests/render_test.py +++ b/tests/python_tests/render_test.py @@ -127,6 +127,8 @@ def test_render_grid(): r = mapnik.Rule() #symb = mapnik.PointSymbolizer() symb = mapnik.MarkersSymbolizer() + symb.width = 10 + symb.height = 10 symb.allow_overlap = True r.symbols.append(symb) label = mapnik.TextSymbolizer(mapnik.Expression('[Name]'),