diff --git a/include/mapnik/extend_converter.hpp b/include/mapnik/extend_converter.hpp new file mode 100644 index 000000000..a7ac187eb --- /dev/null +++ b/include/mapnik/extend_converter.hpp @@ -0,0 +1,232 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2015 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_EXTEND_CONVERTER_HPP +#define MAPNIK_EXTEND_CONVERTER_HPP + +#include + +#pragma GCC diagnostic push +#include +#include +#include +#include +#include +#pragma GCC diagnostic pop + +// stl +#include + +namespace mapnik +{ + +namespace detail +{ + +namespace msm = boost::msm; +namespace mpl = boost::mpl; +using namespace msm::front; + +template +T extend(T const & v1, T const & v2, double length) +{ + double dx = v2.x - v1.x; + double dy = v2.y - v1.y; + double l12 = std::sqrt(dx * dx + dy * dy); + double coef = 1.0 + length / l12; + return vertex2d(v1.x + dx * coef, + v1.y + dy * coef, v2.cmd); +} + +namespace events +{ + struct vertex_event + { + vertex_event(vertex2d const & vertex) : vertex(vertex) { } + vertex2d const & vertex; + }; + + struct move_to : vertex_event { using vertex_event::vertex_event; }; + struct line_to : vertex_event { using vertex_event::vertex_event; }; + struct close : vertex_event { using vertex_event::vertex_event; }; + struct end : vertex_event { using vertex_event::vertex_event; }; +} + +namespace actions +{ + struct store + { + template + void operator()(EVT const & e, FSM & m, SourceState&, TargetState&) + { + m.v2 = m.v1; + m.v1 = e.vertex; + m.output = boost::none; + } + }; + + struct output + { + template + void operator()(EVT const & e, FSM & m, SourceState&, TargetState&) + { + m.output = e.vertex; + } + }; + + struct store_and_output + { + template + void operator()(EVT const & e, FSM & m, SourceState&, TargetState&) + { + m.v2 = m.v1; + m.v1 = e.vertex; + m.output = m.v2; + } + }; + + struct output_begin + { + template + void operator()(EVT const & e, FSM & m, SourceState&, TargetState&) + { + m.v2 = m.v1; + m.v1 = e.vertex; + m.output = extend(m.v1, m.v2, m.extend_length); + } + }; + + struct output_end + { + template + void operator()(EVT const & e, FSM & m, SourceState&, TargetState&) + { + m.output = extend(m.v2, m.v1, m.extend_length); + m.v1 = e.vertex; + } + }; +} + +struct extender_def : public msm::front::state_machine_def +{ + using no_exception_thrown = int; + using no_message_queue = int; + + struct initial : public msm::front::state<> { }; + struct vertex_one : public msm::front::state<> { }; + struct vertex_two : public msm::front::state<> { }; + struct end : public msm::front::state<> { }; + + using initial_state = initial; + + struct transition_table : mpl::vector< + // Start Event Next Action Guard + // +------------+-----------------+------------+--------------------+------+ + Row < initial , events::move_to , vertex_one , actions::store >, + Row < initial , events::line_to , vertex_one , actions::store >, + Row < initial , events::close , initial >, + Row < initial , events::end , end , actions::output >, + Row < vertex_one , events::move_to , vertex_one , actions::store_and_output >, + Row < vertex_one , events::line_to , vertex_two , actions::output_begin >, + Row < vertex_one , events::close , initial , actions::store_and_output >, + Row < vertex_one , events::end , end , actions::store_and_output >, + Row < vertex_two , events::move_to , vertex_one , actions::output_end >, + Row < vertex_two , events::line_to , vertex_two , actions::store_and_output >, + Row < vertex_two , events::close , initial , actions::output_end >, + Row < vertex_two , events::end , end , actions::output_end >, + Row < end , events::end , end , actions::output > + > {}; + + extender_def(double extend_length) + : extend_length(extend_length) + { + } + + boost::optional output; + vertex2d v1, v2; + double extend_length; +}; + +using extender = msm::back::state_machine; + +} + +template +struct extend_converter +{ + extend_converter(Geometry & geom) + : extend_converter(geom, 0) + {} + + extend_converter(Geometry & geom, double extend) + : geom_(geom), extender_(extend) + {} + + void set_extend(double extend) + { + extender_.extend_length = extend; + } + + unsigned vertex(double * x, double * y) + { + using namespace detail; + vertex2d v; + do + { + v.cmd = geom_.vertex(&v.x, &v.y); + switch (v.cmd) + { + case SEG_MOVETO: + extender_.process_event(events::move_to(v)); + break; + case SEG_LINETO: + extender_.process_event(events::line_to(v)); + break; + case SEG_CLOSE: + extender_.process_event(events::close(v)); + break; + case SEG_END: + extender_.process_event(events::end(v)); + break; + } + } while(!extender_.output); + + vertex2d const & output = *extender_.output; + *x = output.x; + *y = output.y; + return output.cmd; + } + + void rewind(unsigned) + { + geom_.rewind(0); + extender_.start(); + } + +private: + Geometry & geom_; + detail::extender extender_; +}; + +} + +#endif // MAPNIK_EXTEND_CONVERTER_HPP diff --git a/include/mapnik/symbolizer_default_values.hpp b/include/mapnik/symbolizer_default_values.hpp index 07b31fed5..3f918fb2f 100644 --- a/include/mapnik/symbolizer_default_values.hpp +++ b/include/mapnik/symbolizer_default_values.hpp @@ -369,6 +369,13 @@ struct symbolizer_default // font-feature-settings +// extend +template <> +struct symbolizer_default +{ + static value_double value() { return 0.0; } +}; + } // namespace mapnik #endif // MAPNIK_SYMBOLIZER_DEFAULT_VALUES_HPP diff --git a/include/mapnik/symbolizer_keys.hpp b/include/mapnik/symbolizer_keys.hpp index 217fff1d0..1d395c015 100644 --- a/include/mapnik/symbolizer_keys.hpp +++ b/include/mapnik/symbolizer_keys.hpp @@ -92,6 +92,7 @@ enum class keys : std::uint8_t direction, avoid_edges, ff_settings, + extend, MAX_SYMBOLIZER_KEY }; diff --git a/include/mapnik/text/symbolizer_helpers.hpp b/include/mapnik/text/symbolizer_helpers.hpp index 3e7a4dc3a..b5c3df0ad 100644 --- a/include/mapnik/text/symbolizer_helpers.hpp +++ b/include/mapnik/text/symbolizer_helpers.hpp @@ -59,7 +59,7 @@ struct placement_finder_adapter }; -using vertex_converter_type = vertex_converter; +using vertex_converter_type = vertex_converter; class base_symbolizer_helper { diff --git a/include/mapnik/vertex_converters.hpp b/include/mapnik/vertex_converters.hpp index db0428788..05c0d74fc 100644 --- a/include/mapnik/vertex_converters.hpp +++ b/include/mapnik/vertex_converters.hpp @@ -36,6 +36,7 @@ #include #include #include +#include #pragma GCC diagnostic push #include @@ -65,6 +66,7 @@ struct stroke_tag {}; struct dash_tag {}; struct affine_transform_tag {}; struct offset_transform_tag {}; +struct extend_tag {}; namespace detail { @@ -254,6 +256,23 @@ struct converter_traits } }; +template +struct converter_traits +{ + using geometry_type = T; + using conv_type = extend_converter; + + template + static void setup(geometry_type & geom, Args const& args) + { + auto const& sym = args.sym; + auto const& feat = args.feature; + auto const& vars = args.vars; + double extend = get(sym, feat, vars); + geom.set_extend(extend * args.scale_factor); + } +}; + template struct is_switchable diff --git a/src/load_map.cpp b/src/load_map.cpp index 566caae88..c5f48e4ed 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -895,6 +895,7 @@ void map_parser::parse_symbolizer_base(symbolizer_base &sym, xml_node const& nod set_symbolizer_property(sym, keys::comp_op, node); set_symbolizer_property(sym, keys::geometry_transform, node); set_symbolizer_property(sym, keys::simplify_algorithm, node); + set_symbolizer_property(sym, keys::extend, node); } void map_parser::parse_point_symbolizer(rule & rule, xml_node const & node) diff --git a/src/symbolizer_keys.cpp b/src/symbolizer_keys.cpp index 46d15fa4c..db04a296a 100644 --- a/src/symbolizer_keys.cpp +++ b/src/symbolizer_keys.cpp @@ -158,6 +158,7 @@ static const property_meta_type key_meta[const_max_key] = property_types::target_direction}, property_meta_type{ "avoid-edges",nullptr, property_types::target_bool }, property_meta_type{ "font-feature-settings", nullptr, property_types::target_font_feature_settings }, + property_meta_type{ "extend", nullptr, property_types::target_double}, }; diff --git a/src/text/symbolizer_helpers.cpp b/src/text/symbolizer_helpers.cpp index b8857f933..17b4cec08 100644 --- a/src/text/symbolizer_helpers.cpp +++ b/src/text/symbolizer_helpers.cpp @@ -327,10 +327,12 @@ text_symbolizer_helper::text_symbolizer_helper( value_bool clip = mapnik::get(sym_, feature_, vars_); value_double simplify_tolerance = mapnik::get(sym_, feature_, vars_); value_double smooth = mapnik::get(sym_, feature_, vars_); + value_double extend = mapnik::get(sym_, feature_, vars_); if (clip) converter_.template set(); converter_.template set(); //always transform converter_.template set(); + if (extend > 0.0) converter_.template set(); if (simplify_tolerance > 0.0) converter_.template set(); // optional simplify converter if (smooth > 0.0) converter_.template set(); // optional smooth converter @@ -452,12 +454,15 @@ text_symbolizer_helper::text_symbolizer_helper( value_bool clip = mapnik::get(sym_, feature_, vars_); value_double simplify_tolerance = mapnik::get(sym_, feature_, vars_); value_double smooth = mapnik::get(sym_, feature_, vars_); + value_double extend = mapnik::get(sym_, feature_, vars_); if (clip) converter_.template set(); converter_.template set(); //always transform converter_.template set(); + if (extend > 0.0) converter_.template set(); if (simplify_tolerance > 0.0) converter_.template set(); // optional simplify converter if (smooth > 0.0) converter_.template set(); // optional smooth converter + if (geometries_to_process_.size()) { init_marker(); diff --git a/test/unit/vertex_adapter/extend_converter.cpp b/test/unit/vertex_adapter/extend_converter.cpp new file mode 100644 index 000000000..a8b0f080d --- /dev/null +++ b/test/unit/vertex_adapter/extend_converter.cpp @@ -0,0 +1,128 @@ +#include "catch.hpp" +#include "fake_path.hpp" + +// mapnik +#include + +// stl +#include + +namespace offset_test { + +TEST_CASE("extend converter") { + +SECTION("empty") { + try + { + fake_path path = {}; + mapnik::extend_converter c(path, 1000); + double x, y; + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_END); + } + catch (std::exception const& ex) + { + std::cerr << ex.what() << "\n"; + REQUIRE(false); + } +} + +SECTION("one point") { + try + { + fake_path path = { 0, 0 }; + mapnik::extend_converter c(path, 1000); + double x, y; + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_MOVETO); + REQUIRE(x == 0); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_END); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_END); + } + catch (std::exception const& ex) + { + std::cerr << ex.what() << "\n"; + REQUIRE(false); + } +} + +SECTION("two points") { + try + { + fake_path path = { 0, 0 , 1, 0}; + mapnik::extend_converter c(path, 1000); + double x, y; + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_MOVETO); + REQUIRE(x == -1000); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_LINETO); + REQUIRE(x == 1001); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_END); + } + catch (std::exception const& ex) + { + std::cerr << ex.what() << "\n"; + REQUIRE(false); + } +} + +SECTION("three points") { + try + { + fake_path path = { 0, 0, 1, 0, 2, 0 }; + mapnik::extend_converter c(path, 1000); + double x, y; + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_MOVETO); + REQUIRE(x == -1000); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_LINETO); + REQUIRE(x == 1); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_LINETO); + REQUIRE(x == 1002); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_END); + } + catch (std::exception const& ex) + { + std::cerr << ex.what() << "\n"; + REQUIRE(false); + } +} + +SECTION("more points") { + try + { + fake_path path = { 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0 }; + mapnik::extend_converter c(path, 1000); + double x, y; + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_MOVETO); + REQUIRE(x == -1000); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_LINETO); + REQUIRE(x == 1); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_LINETO); + REQUIRE(x == 2); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_LINETO); + REQUIRE(x == 3); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_LINETO); + REQUIRE(x == 4); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_LINETO); + REQUIRE(x == 1005); + REQUIRE(y == 0); + REQUIRE(c.vertex(&x, &y) == mapnik::SEG_END); + } + catch (std::exception const& ex) + { + std::cerr << ex.what() << "\n"; + REQUIRE(false); + } +} + +} + +} diff --git a/test/unit/vertex_adapter/fake_path.hpp b/test/unit/vertex_adapter/fake_path.hpp new file mode 100644 index 000000000..dc1b4aed2 --- /dev/null +++ b/test/unit/vertex_adapter/fake_path.hpp @@ -0,0 +1,93 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2015 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 + +// stl +#include +#include + +namespace detail +{ + +template +struct fake_path +{ + using coord_type = std::tuple; + using cont_type = std::vector; + cont_type vertices_; + typename cont_type::iterator itr_; + + fake_path(std::initializer_list l) + : fake_path(l.begin(), l.size()) + { + } + + fake_path(std::vector const &v, bool make_invalid = false) + : fake_path(v.begin(), v.size(), make_invalid) + { + } + + template + fake_path(Itr itr, size_t sz, bool make_invalid = false) + { + size_t num_coords = sz >> 1; + vertices_.reserve(num_coords + (make_invalid ? 1 : 0)); + if (make_invalid) + { + vertices_.push_back(std::make_tuple(0,0,mapnik::SEG_END)); + } + + for (size_t i = 0; i < num_coords; ++i) + { + T x = *itr++; + T y = *itr++; + unsigned cmd = (i == 0) ? mapnik::SEG_MOVETO : mapnik::SEG_LINETO; + vertices_.push_back(std::make_tuple(x, y, cmd)); + } + itr_ = vertices_.begin(); + } + + unsigned vertex(T *x, T *y) + { + if (itr_ == vertices_.end()) + { + return mapnik::SEG_END; + } + *x = std::get<0>(*itr_); + *y = std::get<1>(*itr_); + unsigned cmd = std::get<2>(*itr_); + ++itr_; + return cmd; + } + + void rewind(unsigned) + { + itr_ = vertices_.begin(); + } +}; + +} + +using fake_path = detail::fake_path; + diff --git a/test/unit/vertex_adapter/line_offset_test.cpp b/test/unit/vertex_adapter/line_offset_test.cpp index dcb8dc68b..75bfb0799 100644 --- a/test/unit/vertex_adapter/line_offset_test.cpp +++ b/test/unit/vertex_adapter/line_offset_test.cpp @@ -1,5 +1,6 @@ #include "catch.hpp" +#include "fake_path.hpp" // mapnik #include @@ -7,53 +8,6 @@ // stl #include -#include -#include - -struct fake_path -{ - using coord_type = std::tuple; - using cont_type = std::vector; - cont_type vertices_; - cont_type::iterator itr_; - - fake_path(std::initializer_list l) - : fake_path(l.begin(), l.size()) { - } - - fake_path(std::vector const &v) - : fake_path(v.begin(), v.size()) { - } - - template - fake_path(Itr itr, size_t sz) { - size_t num_coords = sz >> 1; - vertices_.reserve(num_coords); - - for (size_t i = 0; i < num_coords; ++i) { - double x = *itr++; - double y = *itr++; - unsigned cmd = (i == 0) ? agg::path_cmd_move_to : agg::path_cmd_line_to; - vertices_.push_back(std::make_tuple(x, y, cmd)); - } - itr_ = vertices_.begin(); - } - - unsigned vertex(double *x, double *y) { - if (itr_ == vertices_.end()) { - return agg::path_cmd_stop; - } - *x = std::get<0>(*itr_); - *y = std::get<1>(*itr_); - unsigned cmd = std::get<2>(*itr_); - ++itr_; - return cmd; - } - - void rewind(unsigned) { - itr_ = vertices_.begin(); - } -}; double dist(mapnik::pixel_position const &a, mapnik::pixel_position const &b) diff --git a/test/unit/vertex_adapter/offset_converter.cpp b/test/unit/vertex_adapter/offset_converter.cpp index 9255f7d97..96c796d06 100644 --- a/test/unit/vertex_adapter/offset_converter.cpp +++ b/test/unit/vertex_adapter/offset_converter.cpp @@ -1,66 +1,15 @@ #include "catch.hpp" +#include "fake_path.hpp" // mapnik -#include #include // stl #include -#include -#include namespace offset_test { -struct fake_path -{ - using coord_type = std::tuple; - using cont_type = std::vector; - cont_type vertices_; - cont_type::iterator itr_; - - fake_path(std::initializer_list l) - : fake_path(l.begin(), l.size()) { - } - - fake_path(std::vector const &v, bool make_invalid = false) - : fake_path(v.begin(), v.size(), make_invalid) { - } - - template - fake_path(Itr itr, size_t sz, bool make_invalid = false) { - size_t num_coords = sz >> 1; - vertices_.reserve(num_coords + (make_invalid ? 1 : 0)); - if (make_invalid) - { - vertices_.push_back(std::make_tuple(0,0,mapnik::SEG_END)); - } - - for (size_t i = 0; i < num_coords; ++i) { - double x = *itr++; - double y = *itr++; - unsigned cmd = (i == 0) ? mapnik::SEG_MOVETO : mapnik::SEG_LINETO; - vertices_.push_back(std::make_tuple(x, y, cmd)); - } - itr_ = vertices_.begin(); - } - - unsigned vertex(double *x, double *y) { - if (itr_ == vertices_.end()) { - return mapnik::SEG_END; - } - *x = std::get<0>(*itr_); - *y = std::get<1>(*itr_); - unsigned cmd = std::get<2>(*itr_); - ++itr_; - return cmd; - } - - void rewind(unsigned) { - itr_ = vertices_.begin(); - } -}; - static double DELTA_BUFF = 0.5; double dist(double x0, double y0, double x1, double y1)