From 8b55943c1c3c9e3832e231de5ff54a29b7e0df41 Mon Sep 17 00:00:00 2001 From: Jochen Topf Date: Thu, 8 Sep 2022 09:54:39 +0200 Subject: [PATCH] Add reverse() function to reverse linestring (and other) geometries --- src/flex-lua-geom.cpp | 15 ++++++++ src/geom-functions.cpp | 56 ++++++++++++++++++++++++++++ src/geom-functions.hpp | 16 ++++++++ tests/test-geom-linestrings.cpp | 13 +++++++ tests/test-geom-multilinestrings.cpp | 5 +++ tests/test-geom-multipoints.cpp | 2 + tests/test-geom-null.cpp | 1 + tests/test-geom-points.cpp | 1 + tests/test-geom-polygons.cpp | 9 +++++ 9 files changed, 118 insertions(+) diff --git a/src/flex-lua-geom.cpp b/src/flex-lua-geom.cpp index fbde84245..32ad2704c 100644 --- a/src/flex-lua-geom.cpp +++ b/src/flex-lua-geom.cpp @@ -119,6 +119,20 @@ static int geom_is_null(lua_State *lua_state) return 1; } +static int geom_reverse(lua_State *lua_state) +{ + auto const *const input_geometry = unpack_geometry(lua_state); + + try { + auto *geom = create_lua_geometry_object(lua_state); + geom::reverse(geom, *input_geometry); + } catch (...) { + return luaL_error(lua_state, "Unknown error in 'reverse()'.\n"); + } + + return 1; +} + static int geom_line_merge(lua_State *lua_state) { auto const *const input_geometry = unpack_geometry(lua_state); @@ -230,6 +244,7 @@ void init_geometry_class(lua_State *lua_state) luaX_add_table_func(lua_state, "geometry_type", geom_geometry_type); luaX_add_table_func(lua_state, "is_null", geom_is_null); luaX_add_table_func(lua_state, "line_merge", geom_line_merge); + luaX_add_table_func(lua_state, "reverse", geom_reverse); luaX_add_table_func(lua_state, "num_geometries", geom_num_geometries); luaX_add_table_func(lua_state, "segmentize", geom_segmentize); luaX_add_table_func(lua_state, "simplify", geom_simplify); diff --git a/src/geom-functions.cpp b/src/geom-functions.cpp index fc5f581d0..cdb92a21b 100644 --- a/src/geom-functions.cpp +++ b/src/geom-functions.cpp @@ -446,6 +446,62 @@ static void add_nodes_to_linestring(linestring_t *linestring, ITERATOR it, } } +/****************************************************************************/ + +static void reverse(geom::nullgeom_t * /*output*/, + geom::nullgeom_t const & /*input*/) noexcept +{} + +static void reverse(geom::point_t *output, geom::point_t const &input) noexcept +{ + *output = input; +} + +static void reverse(point_list_t *output, point_list_t const &input) +{ + output->reserve(input.size()); + std::reverse_copy(input.cbegin(), input.cend(), + std::back_inserter(*output)); +} + +static void reverse(geom::polygon_t *output, geom::polygon_t const &input) +{ + reverse(&output->outer(), input.outer()); + for (auto const &g : input.inners()) { + reverse(&output->inners().emplace_back(), g); + } +} + +template +void reverse(geom::multigeometry_t *output, + geom::multigeometry_t const &input) +{ + output->reserve(input.num_geometries()); + for (auto const &g : input) { + reverse(&output->add_geometry(), g); + } +} + +void reverse(geometry_t *output, geometry_t const &input) +{ + output->set_srid(input.srid()); + + input.visit([&](auto const &geom) { + using inner_type = + std::remove_const_t>; + return reverse(&output->set(), geom); + }); +} + +geometry_t reverse(geometry_t const &input) +{ + geometry_t output; + reverse(&output, input); + return output; +} + +/****************************************************************************/ + void line_merge(geometry_t *output, geometry_t const &input) { if (input.is_linestring()) { diff --git a/src/geom-functions.hpp b/src/geom-functions.hpp index fafbe37e7..ef1e3ffee 100644 --- a/src/geom-functions.hpp +++ b/src/geom-functions.hpp @@ -158,6 +158,22 @@ double area(geometry_t const &geom); */ std::vector split_multi(geometry_t &&geom, bool split_multi = true); +/** + * Reverses the order of the vertices in geometry. + * + * \param output Pointer to output geometry. + * \param input Input geometry. + */ +void reverse(geometry_t *output, geometry_t const &input); + +/** + * Reverses the order of the vertices in geometry. + * + * \param input Input geometry. + * \returns Result geometry. + */ +geometry_t reverse(geometry_t const &input); + /** * Merge lines in a multilinestring end-to-end as far as possible. * diff --git a/tests/test-geom-linestrings.cpp b/tests/test-geom-linestrings.cpp index 5f1c53906..799d63d89 100644 --- a/tests/test-geom-linestrings.cpp +++ b/tests/test-geom-linestrings.cpp @@ -49,6 +49,19 @@ TEST_CASE("line geometry", "[NoDB]") REQUIRE(geometry_n(geom, 1) == geom); } +TEST_CASE("reverse line geometry", "[NoDB]") +{ + geom::geometry_t const geom{geom::linestring_t{{1, 1}, {2, 2}}}; + + auto reversed = geom::reverse(geom); + REQUIRE(num_geometries(reversed) == 1); + REQUIRE(geometry_type(reversed) == "LINESTRING"); + + auto const &line = reversed.get(); + REQUIRE(line.size() == 2); + REQUIRE(line == geom::linestring_t{{2, 2}, {1, 1}}); +} + TEST_CASE("create_linestring from OSM data", "[NoDB]") { test_buffer_t buffer; diff --git a/tests/test-geom-multilinestrings.cpp b/tests/test-geom-multilinestrings.cpp index 7d5776c2e..5613f7767 100644 --- a/tests/test-geom-multilinestrings.cpp +++ b/tests/test-geom-multilinestrings.cpp @@ -20,6 +20,7 @@ TEST_CASE("create_multilinestring with single line", "[NoDB]") { geom::linestring_t const expected{{1, 1}, {2, 1}}; + geom::linestring_t const expected_rev{{2, 1}, {1, 1}}; test_buffer_t buffer; buffer.add_way("w20 Nn10x1y1,n11x2y1"); @@ -34,6 +35,10 @@ TEST_CASE("create_multilinestring with single line", "[NoDB]") auto const &ml = geom.get(); REQUIRE(ml.num_geometries() == 1); REQUIRE(ml[0] == expected); + + auto const rev = reverse(geom); + REQUIRE(rev.is_multilinestring()); + REQUIRE(rev.get()[0] == expected_rev); } TEST_CASE("create_multilinestring with single line and no force_multi", diff --git a/tests/test-geom-multipoints.cpp b/tests/test-geom-multipoints.cpp index 10467d720..853e8f9a3 100644 --- a/tests/test-geom-multipoints.cpp +++ b/tests/test-geom-multipoints.cpp @@ -30,6 +30,7 @@ TEST_CASE("multipoint_t with a single point", "[NoDB]") REQUIRE(geometry_type(geom) == "MULTIPOINT"); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(0.0)); + REQUIRE(reverse(geom) == geom); REQUIRE(centroid(geom) == geom::geometry_t{std::move(point)}); REQUIRE(mp[0] == expected); @@ -51,6 +52,7 @@ TEST_CASE("multipoint_t with several points", "[NoDB]") REQUIRE(geometry_type(geom) == "MULTIPOINT"); REQUIRE(num_geometries(geom) == 3); REQUIRE(area(geom) == Approx(0.0)); + REQUIRE(reverse(geom) == geom); REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{2, 1}}); REQUIRE(mp[0] == p0); diff --git a/tests/test-geom-null.cpp b/tests/test-geom-null.cpp index 6c11e7de1..fa9f456a6 100644 --- a/tests/test-geom-null.cpp +++ b/tests/test-geom-null.cpp @@ -22,4 +22,5 @@ TEST_CASE("null geometry", "[NoDB]") REQUIRE(geometry_type(geom) == "NULL"); REQUIRE(centroid(geom).is_null()); REQUIRE(geometry_n(geom, 1).is_null()); + REQUIRE(reverse(geom).is_null()); } diff --git a/tests/test-geom-points.cpp b/tests/test-geom-points.cpp index 448acbab5..c0edeee29 100644 --- a/tests/test-geom-points.cpp +++ b/tests/test-geom-points.cpp @@ -54,6 +54,7 @@ TEST_CASE("create_point from OSM data", "[NoDB]") REQUIRE(area(geom) == Approx(0.0)); REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1.1, 2.2}}); REQUIRE(geometry_n(geom, 1) == geom); + REQUIRE(reverse(geom) == geom); REQUIRE(geom.get() == geom::point_t{1.1, 2.2}); } diff --git a/tests/test-geom-polygons.cpp b/tests/test-geom-polygons.cpp index a288c355a..2b173dce8 100644 --- a/tests/test-geom-polygons.cpp +++ b/tests/test-geom-polygons.cpp @@ -66,6 +66,15 @@ TEST_CASE("geom::polygon_t", "[NoDB]") REQUIRE(area(geom) == Approx(8.0)); REQUIRE(geometry_type(geom) == "POLYGON"); REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1.5, 1.5}}); + + auto const geom_rev = reverse(geom); + REQUIRE(geom_rev.is_polygon()); + auto const &rev = geom_rev.get(); + REQUIRE(rev.outer() == + geom::ring_t{{0, 0}, {3, 0}, {3, 3}, {0, 3}, {0, 0}}); + REQUIRE(rev.inners().size() == 1); + REQUIRE(rev.inners()[0] == + geom::ring_t{{1, 1}, {1, 2}, {2, 2}, {2, 1}, {1, 1}}); } TEST_CASE("create_polygon from OSM data", "[NoDB]")