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/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); + } +} + +} + +}