From 2648f8f49ac0fd178442fb7c4c8181298485f870 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 20 Feb 2013 19:52:54 -0800 Subject: [PATCH 1/5] first stab at moving mutable render request properties out of apply_to_layer --- include/mapnik/agg_renderer.hpp | 3 + include/mapnik/feature_style_processor.hpp | 5 + .../mapnik/feature_style_processor_impl.hpp | 45 ++++-- include/mapnik/request.hpp | 128 +++++++++++++++++ src/agg/agg_renderer.cpp | 19 +++ src/build.py | 1 + src/request.cpp | 132 ++++++++++++++++++ 7 files changed, 323 insertions(+), 10 deletions(-) create mode 100644 include/mapnik/request.hpp create mode 100644 src/request.cpp diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index b40a65658..f56e992b6 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -34,6 +34,7 @@ #include // for CoordTransform #include // for composite_mode_e #include +#include // boost #include @@ -69,6 +70,8 @@ public: // create with external placement detector, possibly non-empty agg_renderer(Map const &m, T & pixmap, boost::shared_ptr detector, double scale_factor=1.0, unsigned offset_x=0, unsigned offset_y=0); + // create with mapnik.request object that holds request-specific stuff + agg_renderer(Map const& m, request const& req, T & pixmap, double scale_factor=1.0); ~agg_renderer(); void start_map_processing(Map const& map); void end_map_processing(Map const& map); diff --git a/include/mapnik/feature_style_processor.hpp b/include/mapnik/feature_style_processor.hpp index 0398cd7a5..1c0c52a53 100644 --- a/include/mapnik/feature_style_processor.hpp +++ b/include/mapnik/feature_style_processor.hpp @@ -68,7 +68,12 @@ public: void apply_to_layer(layer const& lay, Processor & p, projection const& proj0, + double scale, double scale_denom, + unsigned width, + unsigned height, + box2d const& extent, + int buffer_size, std::set& names); private: diff --git a/include/mapnik/feature_style_processor_impl.hpp b/include/mapnik/feature_style_processor_impl.hpp index ee9dbdaab..d176258e8 100644 --- a/include/mapnik/feature_style_processor_impl.hpp +++ b/include/mapnik/feature_style_processor_impl.hpp @@ -171,7 +171,17 @@ void feature_style_processor::apply() if (lyr.visible(scale_denom)) { std::set names; - apply_to_layer(lyr, p, proj, scale_denom, names); + apply_to_layer(lyr, + p, + proj, + m_.scale(), + scale_denom, + m_.width(), + m_.height(), + m_.get_current_extent(), + m_.buffer_size(), + names); + } } } @@ -202,7 +212,16 @@ void feature_style_processor::apply(mapnik::layer const& lyr, std::se if (lyr.visible(scale_denom)) { - apply_to_layer(lyr, p, proj, scale_denom, names); + apply_to_layer(lyr, + p, + proj, + m_.scale(), + scale_denom, + m_.width(), + m_.height(), + m_.get_current_extent(), + m_.buffer_size(), + names); } } catch (proj_init_error& ex) @@ -215,7 +234,12 @@ void feature_style_processor::apply(mapnik::layer const& lyr, std::se template void feature_style_processor::apply_to_layer(layer const& lay, Processor & p, projection const& proj0, + double scale, double scale_denom, + unsigned width, + unsigned height, + box2d const& extent, + int buffer_size, std::set& names) { std::vector const& style_names = lay.styles(); @@ -253,20 +277,21 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces #endif - box2d query_ext = m_.get_current_extent(); // unbuffered + box2d query_ext = extent; // unbuffered box2d buffered_query_ext(query_ext); // buffered + double buffer_padding = 2.0 * scale; boost::optional layer_buffer_size = lay.buffer_size(); if (layer_buffer_size) // if layer overrides buffer size, use this value to compute buffered extent { - double extra = 2.0 * m_.scale() * *layer_buffer_size; - buffered_query_ext.width(query_ext.width() + extra); - buffered_query_ext.height(query_ext.height() + extra); + buffer_padding *= *layer_buffer_size; } else { - buffered_query_ext = m_.get_buffered_extent(); + buffer_padding *= buffer_size; } + buffered_query_ext.width(query_ext.width() + buffer_padding); + buffered_query_ext.height(query_ext.height() + buffer_padding); // clip buffered extent by maximum extent, if supplied boost::optional > const& maximum_extent = m_.maximum_extent(); @@ -363,10 +388,10 @@ void feature_style_processor::apply_to_layer(layer const& lay, Proces double qw = query_ext.width()>0 ? query_ext.width() : 1; double qh = query_ext.height()>0 ? query_ext.height() : 1; - query::resolution_type res(m_.width()/qw, - m_.height()/qh); + query::resolution_type res(width/qw, + height/qh); - query q(layer_ext,res,scale_denom,m_.get_current_extent()); + query q(layer_ext,res,scale_denom,extent); std::vector active_styles; attribute_collector collector(names); double filt_factor = 1.0; diff --git a/include/mapnik/request.hpp b/include/mapnik/request.hpp new file mode 100644 index 000000000..8d95a600a --- /dev/null +++ b/include/mapnik/request.hpp @@ -0,0 +1,128 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2013 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_REQUEST_HPP +#define MAPNIK_REQUEST_HPP + +// mapnik +//#include +#include +#include + +// boost +#include + +namespace mapnik +{ + +class MAPNIK_DECL request +{ + +private: + static const unsigned MIN_MAPSIZE=16; + static const unsigned MAX_MAPSIZE=MIN_MAPSIZE<<10; + unsigned width_; + unsigned height_; + int buffer_size_; + box2d current_extent_; + boost::optional > maximum_extent_; + +public: + + /*! \brief Constructor + * @param width Initial map width. + * @param height Initial map height. + * @param srs Initial map projection. + */ + request(int width, int height); + + /*! \brief Get map width. + */ + unsigned width() const; + + /*! \brief Get map height. + */ + unsigned height() const; + + /*! \brief Set map width. + */ + void set_width(unsigned width); + + /*! \brief Set map height. + */ + void set_height(unsigned height); + + /*! \brief Resize the map. + */ + void resize(unsigned width,unsigned height); + + /*! \brief Set buffer size + * @param buffer_size Buffer size in pixels. + */ + + void set_buffer_size(int buffer_size); + + /*! \brief Get the map buffer size + * @return Buffer size as int + */ + int buffer_size() const; + + /*! \brief Set the map maximum extent. + * @param box The bounding box for the maximum extent. + */ + void set_maximum_extent(box2d const& box); + + /*! \brief Get the map maximum extent as box2d + */ + boost::optional > const& maximum_extent() const; + + void reset_maximum_extent(); + + /*! \brief Zoom the map to a bounding box. + * + * Aspect is handled automatic if not fitting to width/height. + * @param box The bounding box where to zoom. + */ + void zoom_to_box(const box2d& box); + + /*! \brief Get current bounding box. + * @return The current bounding box. + */ + const box2d& get_current_extent() const; + + /*! \brief Get current buffered bounding box. + * @return The current buffered bounding box. + */ + box2d get_buffered_extent() const; + + /*! + * @return The Map Scale. + */ + double scale() const; + + ~request(); +private: +}; + +} + +#endif // MAPNIK_REQUEST_HPP diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index 12684fdac..fadc509de 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -84,6 +84,25 @@ agg_renderer::agg_renderer(Map const& m, T & pixmap, double scale_factor, uns setup(m); } +template +agg_renderer::agg_renderer(Map const& m, request const& req, T & pixmap, double scale_factor) + : feature_style_processor(m, scale_factor), + pixmap_(pixmap), + internal_buffer_(), + current_buffer_(&pixmap), + style_level_compositing_(false), + width_(pixmap_.width()), + height_(pixmap_.height()), + scale_factor_(scale_factor), + t_(req.width(),req.height(),req.get_current_extent(),0.0,0.0), + font_engine_(), + font_manager_(font_engine_), + detector_(boost::make_shared(box2d(-req.buffer_size(), -req.buffer_size(), req.width() + req.buffer_size() ,req.height() + req.buffer_size()))), + ras_ptr(new rasterizer) +{ + setup(m); +} + template agg_renderer::agg_renderer(Map const& m, T & pixmap, boost::shared_ptr detector, double scale_factor, unsigned offset_x, unsigned offset_y) diff --git a/src/build.py b/src/build.py index 563684559..37a165c0e 100644 --- a/src/build.py +++ b/src/build.py @@ -100,6 +100,7 @@ else: # unix, non-macos source = Split( """ + request.cpp well_known_srs.cpp params.cpp image_filter_types.cpp diff --git a/src/request.cpp b/src/request.cpp new file mode 100644 index 000000000..6bef9b455 --- /dev/null +++ b/src/request.cpp @@ -0,0 +1,132 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2011 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 + * + *****************************************************************************/ + +// mapnik +#include + +namespace mapnik +{ + + +request::request(int width,int height) + : width_(width), + height_(height), + buffer_size_(0) {} + +request::~request() {} + +unsigned request::width() const +{ + return width_; +} + +unsigned request::height() const +{ + return height_; +} + +void request::set_width(unsigned width) +{ + if (width != width_ && + width >= MIN_MAPSIZE && + width <= MAX_MAPSIZE) + { + width_=width; + } +} + +void request::set_height(unsigned height) +{ + if (height != height_ && + height >= MIN_MAPSIZE && + height <= MAX_MAPSIZE) + { + height_=height; + } +} + +void request::resize(unsigned width,unsigned height) +{ + if (width != width_ && + height != height_ && + width >= MIN_MAPSIZE && + width <= MAX_MAPSIZE && + height >= MIN_MAPSIZE && + height <= MAX_MAPSIZE) + { + width_=width; + height_=height; + } +} + +void request::set_buffer_size( int buffer_size) +{ + buffer_size_ = buffer_size; +} + +int request::buffer_size() const +{ + return buffer_size_; +} + +void request::set_maximum_extent(box2d const& box) +{ + maximum_extent_.reset(box); +} + +boost::optional > const& request::maximum_extent() const +{ + return maximum_extent_; +} + +void request::reset_maximum_extent() +{ + maximum_extent_.reset(); +} + +void request::zoom_to_box(const box2d &box) +{ + current_extent_=box; +} + +const box2d& request::get_current_extent() const +{ + return current_extent_; +} + +box2d request::get_buffered_extent() const +{ + double extra = 2.0 * scale() * buffer_size_; + box2d ext(current_extent_); + ext.width(current_extent_.width() + extra); + ext.height(current_extent_.height() + extra); + return ext; +} + +double request::scale() const +{ + if (width_>0) + return current_extent_.width()/width_; + return current_extent_.width(); +} + +} From 00a2f5434670e7d4d9c96703a4de58a703dff15e Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 25 Feb 2013 19:33:08 -0500 Subject: [PATCH 2/5] further refine the mapnik::request object which can be passed to custom agg_renderer --- include/mapnik/agg_renderer.hpp | 4 +- include/mapnik/request.hpp | 86 ++---------- src/agg/agg_renderer.cpp | 4 +- src/request.cpp | 77 ++-------- tests/cpp_tests/map_request_test.cpp | 132 ++++++++++++++++++ .../map-request-marker-text-line-expected.png | Bin 0 -> 19010 bytes tests/data/good_maps/marker-text-line.xml | 2 +- 7 files changed, 161 insertions(+), 144 deletions(-) create mode 100644 tests/cpp_tests/map_request_test.cpp create mode 100644 tests/cpp_tests/support/map-request-marker-text-line-expected.png diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index f56e992b6..2126cd88e 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -70,8 +70,8 @@ public: // create with external placement detector, possibly non-empty agg_renderer(Map const &m, T & pixmap, boost::shared_ptr detector, double scale_factor=1.0, unsigned offset_x=0, unsigned offset_y=0); - // create with mapnik.request object that holds request-specific stuff - agg_renderer(Map const& m, request const& req, T & pixmap, double scale_factor=1.0); + // pass in mapnik::request object to provide the mutable things per render + agg_renderer(Map const& m, request const& req, T & pixmap, double scale_factor=1.0, unsigned offset_x=0, unsigned offset_y=0); ~agg_renderer(); void start_map_processing(Map const& map); void end_map_processing(Map const& map); diff --git a/include/mapnik/request.hpp b/include/mapnik/request.hpp index 8d95a600a..9a4b99cf0 100644 --- a/include/mapnik/request.hpp +++ b/include/mapnik/request.hpp @@ -24,7 +24,6 @@ #define MAPNIK_REQUEST_HPP // mapnik -//#include #include #include @@ -36,91 +35,24 @@ namespace mapnik class MAPNIK_DECL request { - -private: - static const unsigned MIN_MAPSIZE=16; - static const unsigned MAX_MAPSIZE=MIN_MAPSIZE<<10; - unsigned width_; - unsigned height_; - int buffer_size_; - box2d current_extent_; - boost::optional > maximum_extent_; - public: - - /*! \brief Constructor - * @param width Initial map width. - * @param height Initial map height. - * @param srs Initial map projection. - */ - request(int width, int height); - - /*! \brief Get map width. - */ + request(unsigned width, + unsigned height, + box2d const& extent); unsigned width() const; - - /*! \brief Get map height. - */ unsigned height() const; - - /*! \brief Set map width. - */ - void set_width(unsigned width); - - /*! \brief Set map height. - */ - void set_height(unsigned height); - - /*! \brief Resize the map. - */ - void resize(unsigned width,unsigned height); - - /*! \brief Set buffer size - * @param buffer_size Buffer size in pixels. - */ - void set_buffer_size(int buffer_size); - - /*! \brief Get the map buffer size - * @return Buffer size as int - */ int buffer_size() const; - - /*! \brief Set the map maximum extent. - * @param box The bounding box for the maximum extent. - */ - void set_maximum_extent(box2d const& box); - - /*! \brief Get the map maximum extent as box2d - */ - boost::optional > const& maximum_extent() const; - - void reset_maximum_extent(); - - /*! \brief Zoom the map to a bounding box. - * - * Aspect is handled automatic if not fitting to width/height. - * @param box The bounding box where to zoom. - */ - void zoom_to_box(const box2d& box); - - /*! \brief Get current bounding box. - * @return The current bounding box. - */ - const box2d& get_current_extent() const; - - /*! \brief Get current buffered bounding box. - * @return The current buffered bounding box. - */ + box2d const& extent() const; + void set_extent(box2d const& box); box2d get_buffered_extent() const; - - /*! - * @return The Map Scale. - */ double scale() const; - ~request(); private: + unsigned width_; + unsigned height_; + box2d extent_; + int buffer_size_; }; } diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index fadc509de..e538a9256 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -85,7 +85,7 @@ agg_renderer::agg_renderer(Map const& m, T & pixmap, double scale_factor, uns } template -agg_renderer::agg_renderer(Map const& m, request const& req, T & pixmap, double scale_factor) +agg_renderer::agg_renderer(Map const& m, request const& req, T & pixmap, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m, scale_factor), pixmap_(pixmap), internal_buffer_(), @@ -94,7 +94,7 @@ agg_renderer::agg_renderer(Map const& m, request const& req, T & pixmap, doub width_(pixmap_.width()), height_(pixmap_.height()), scale_factor_(scale_factor), - t_(req.width(),req.height(),req.get_current_extent(),0.0,0.0), + t_(req.width(),req.height(),req.extent(),offset_x,offset_y), font_engine_(), font_manager_(font_engine_), detector_(boost::make_shared(box2d(-req.buffer_size(), -req.buffer_size(), req.width() + req.buffer_size() ,req.height() + req.buffer_size()))), diff --git a/src/request.cpp b/src/request.cpp index 6bef9b455..b99f533de 100644 --- a/src/request.cpp +++ b/src/request.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2011 Artem Pavlenko + * Copyright (C) 2013 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,10 +26,12 @@ namespace mapnik { - -request::request(int width,int height) +request::request(unsigned width, + unsigned height, + box2d const& extent) : width_(width), height_(height), + extent_(extent), buffer_size_(0) {} request::~request() {} @@ -44,41 +46,7 @@ unsigned request::height() const return height_; } -void request::set_width(unsigned width) -{ - if (width != width_ && - width >= MIN_MAPSIZE && - width <= MAX_MAPSIZE) - { - width_=width; - } -} - -void request::set_height(unsigned height) -{ - if (height != height_ && - height >= MIN_MAPSIZE && - height <= MAX_MAPSIZE) - { - height_=height; - } -} - -void request::resize(unsigned width,unsigned height) -{ - if (width != width_ && - height != height_ && - width >= MIN_MAPSIZE && - width <= MAX_MAPSIZE && - height >= MIN_MAPSIZE && - height <= MAX_MAPSIZE) - { - width_=width; - height_=height; - } -} - -void request::set_buffer_size( int buffer_size) +void request::set_buffer_size(int buffer_size) { buffer_size_ = buffer_size; } @@ -88,45 +56,30 @@ int request::buffer_size() const return buffer_size_; } -void request::set_maximum_extent(box2d const& box) +void request::set_extent(box2d const& box) { - maximum_extent_.reset(box); + extent_ = box; } -boost::optional > const& request::maximum_extent() const +box2d const& request::extent() const { - return maximum_extent_; -} - -void request::reset_maximum_extent() -{ - maximum_extent_.reset(); -} - -void request::zoom_to_box(const box2d &box) -{ - current_extent_=box; -} - -const box2d& request::get_current_extent() const -{ - return current_extent_; + return extent_; } box2d request::get_buffered_extent() const { double extra = 2.0 * scale() * buffer_size_; - box2d ext(current_extent_); - ext.width(current_extent_.width() + extra); - ext.height(current_extent_.height() + extra); + box2d ext(extent_); + ext.width(extent_.width() + extra); + ext.height(extent_.height() + extra); return ext; } double request::scale() const { if (width_>0) - return current_extent_.width()/width_; - return current_extent_.width(); + return extent_.width()/width_; + return extent_.width(); } } diff --git a/tests/cpp_tests/map_request_test.cpp b/tests/cpp_tests/map_request_test.cpp new file mode 100644 index 000000000..eb69d1910 --- /dev/null +++ b/tests/cpp_tests/map_request_test.cpp @@ -0,0 +1,132 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool compare_images(std::string const& src_fn,std::string const& dest_fn) +{ + using namespace mapnik; + std::auto_ptr reader1(mapnik::get_image_reader(dest_fn,"png")); + if (!reader1.get()) + { + throw mapnik::image_reader_exception("Failed to load: " + dest_fn); + } + boost::shared_ptr image_ptr1 = boost::make_shared(reader1->width(),reader1->height()); + reader1->read(0,0,image_ptr1->data()); + + std::auto_ptr reader2(mapnik::get_image_reader(src_fn,"png")); + if (!reader2.get()) + { + throw mapnik::image_reader_exception("Failed to load: " + src_fn); + } + boost::shared_ptr image_ptr2 = boost::make_shared(reader2->width(),reader2->height()); + reader2->read(0,0,image_ptr2->data()); + + image_data_32 const& dest = image_ptr1->data(); + image_data_32 const& src = image_ptr2->data(); + + unsigned int width = src.width(); + unsigned int height = src.height(); + if ((width != dest.width()) || height != dest.height()) return false; + for (unsigned int y = 0; y < height; ++y) + { + const unsigned int* row_from = src.getRow(y); + const unsigned int* row_to = dest.getRow(y); + for (unsigned int x = 0; x < width; ++x) + { + if (row_from[x] != row_to[x]) return false; + } + } + return true; +} + +int main( int, char*[] ) +{ + try { + mapnik::datasource_cache::instance().register_datasources("./plugins/input/"); + mapnik::freetype_engine::register_fonts("./fonts", true ); + mapnik::Map m(256,256); + mapnik::load_map(m,"./tests/data/good_maps/marker-text-line.xml",false); + m.zoom_all(); + mapnik::image_32 im(m.width(),m.height()); + double scale_factor = 1.2; + std::string expected("./tests/cpp_tests/support/map-request-marker-text-line-expected.png"); + + // render normally with apply() and just map and image + mapnik::agg_renderer renderer1(m,im,scale_factor); + renderer1.apply(); + std::string actual1("/tmp/map-request-marker-text-line-actual1.png"); + //mapnik::save_to_file(im,expected); + mapnik::save_to_file(im,actual1); + BOOST_TEST(compare_images(actual1,expected)); + + // reset image + im.clear(); + + // set up a mapnik::request object + mapnik::request req(m.width(),m.height(),m.get_current_extent()); + req.set_buffer_size(m.buffer_size()); + + // render using apply() and mapnik::request + mapnik::agg_renderer renderer2(m,req,im,scale_factor); + renderer2.apply(); + std::string actual2("/tmp/map-request-marker-text-line-actual2.png"); + mapnik::save_to_file(im,actual2); + BOOST_TEST(compare_images(actual2,expected)); + + // reset image + im.clear(); + + // render with apply_to_layer api and mapnik::request params passed to apply_to_layer + mapnik::agg_renderer renderer3(m,req,im,scale_factor); + renderer3.start_map_processing(m); + mapnik::projection map_proj(m.srs(),true); + double scale_denom = mapnik::scale_denominator(req.scale(),map_proj.is_geographic()); + scale_denom *= scale_factor; + BOOST_FOREACH ( mapnik::layer const& lyr, m.layers() ) + { + if (lyr.visible(scale_denom)) + { + std::set names; + renderer3.apply_to_layer(lyr, + renderer3, + map_proj, + req.scale(), + scale_denom, + req.width(), + req.height(), + req.extent(), + req.buffer_size(), + names); + + } + } + renderer3.end_map_processing(m); + std::string actual3("/tmp/map-request-marker-text-line-actual3.png"); + mapnik::save_to_file(im,actual3); + BOOST_TEST(compare_images(actual3,expected)); + + } catch (std::exception const& ex) { + std::clog << ex.what() << "\n"; + } + if (!::boost::detail::test_errors()) { + std::clog << "C++ Map Request rendering hook: \x1b[1;32m✓ \x1b[0m\n"; +#if BOOST_VERSION >= 104600 + ::boost::detail::report_errors_remind().called_report_errors_function = true; +#endif + } else { + return ::boost::report_errors(); + } +} diff --git a/tests/cpp_tests/support/map-request-marker-text-line-expected.png b/tests/cpp_tests/support/map-request-marker-text-line-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..2a7be3ddc956dacc899df1843cc53b462a3ebc8b GIT binary patch literal 19010 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 +