Skip to content

Commit 4fc662d

Browse files
committed
Implement simplify for multilinestrings
Use new remove_duplicates() functions to make sure boost::geometry simplify doesn't create something we don't like.
1 parent e1ab481 commit 4fc662d

File tree

5 files changed

+136
-13
lines changed

5 files changed

+136
-13
lines changed

src/geom-functions.cpp

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -727,24 +727,52 @@ geometry_t centroid(geometry_t const &geom)
727727
return output;
728728
}
729729

730-
void simplify(geometry_t *output, geometry_t const &input, double tolerance)
730+
/****************************************************************************/
731+
732+
static bool simplify(linestring_t *output, linestring_t const &input,
733+
double tolerance)
731734
{
732-
if (!input.is_linestring()) {
733-
output->reset();
734-
return;
735+
boost::geometry::simplify(input, *output, tolerance);
736+
737+
// Linestrings with less then 2 points are invalid. Older boost::geometry
738+
// versions will generate a "line" with two identical points. We are
739+
// paranoid here and remove all duplicate points and then check that we
740+
// have at least 2 points.
741+
output->remove_duplicates();
742+
return output->size() > 1;
743+
}
744+
745+
static bool simplify(multilinestring_t *output, multilinestring_t const &input,
746+
double tolerance)
747+
{
748+
for (auto const &ls : input) {
749+
linestring_t simplified_ls;
750+
if (simplify(&simplified_ls, ls, tolerance)) {
751+
output->add_geometry(std::move(simplified_ls));
752+
}
735753
}
754+
return output->num_geometries() > 0;
755+
}
736756

737-
auto &ls = output->set<linestring_t>();
757+
template <typename T>
758+
static bool simplify(T * /*output*/, T const & /*input*/, double /*tolerance*/)
759+
{
760+
return false;
761+
}
762+
763+
void simplify(geometry_t *output, geometry_t const &input, double tolerance)
764+
{
738765
output->set_srid(input.srid());
739766

740-
boost::geometry::simplify(input.get<linestring_t>(), ls, tolerance);
767+
input.visit([&](auto const &input) {
768+
using inner_type =
769+
std::remove_const_t<std::remove_reference_t<decltype(input)>>;
770+
auto &out = output->set<inner_type>();
741771

742-
// Linestrings with less then 2 nodes are invalid. Older boost::geometry
743-
// versions will generate a "line" with two identical points which the
744-
// second check finds.
745-
if (ls.size() < 2 || ls[0] == ls[1]) {
746-
output->reset();
747-
}
772+
if (!simplify(&out, input, tolerance)) {
773+
output->reset();
774+
}
775+
});
748776
}
749777

750778
geometry_t simplify(geometry_t const &input, double tolerance)

src/geom.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414

1515
namespace geom {
1616

17+
void point_list_t::remove_duplicates()
18+
{
19+
auto const it = std::unique(begin(), end());
20+
erase(it, end());
21+
}
22+
1723
bool operator==(polygon_t const &a, polygon_t const &b) noexcept
1824
{
1925
return (a.outer() == b.outer()) && (a.inners() == b.inners());

src/geom.hpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,15 @@ class point_t
9090

9191
}; // class point_t
9292

93-
/// This type is used as the basis for linestrings and rings.
93+
/**
94+
* This type is used as the basis for linestrings and rings.
95+
*
96+
* Point lists should not contain consecutive duplicate points. You can
97+
* use the remove_duplicates() function to remove them if needed. (OGC validity
98+
* only requires there to be at least two different points in a linestring, but
99+
* we are more strict here to make sure we don't have to handle that anomaly
100+
* later on.)
101+
*/
94102
class point_list_t : public std::vector<point_t>
95103
{
96104
public:
@@ -105,6 +113,9 @@ class point_list_t : public std::vector<point_t>
105113
: std::vector<point_t>(list.begin(), list.end())
106114
{}
107115

116+
/// Collapse consecutive identical points into a single point (in-place).
117+
void remove_duplicates();
118+
108119
}; // class point_list_t
109120

110121
class linestring_t : public point_list_t

tests/test-geom-linestrings.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ TEST_CASE("geom::linestring_t", "[NoDB]")
3939
REQUIRE(ls1.num_geometries() == 1);
4040
}
4141

42+
TEST_CASE("remove duplicate points in linestring", "[NoDB]")
43+
{
44+
geom::linestring_t ls{{1, 1}, {1, 2}, {1, 2}, {2, 2}};
45+
REQUIRE(ls.size() == 4);
46+
ls.remove_duplicates();
47+
REQUIRE(ls.size() == 3);
48+
REQUIRE(ls == geom::linestring_t{{1, 1}, {1, 2}, {2, 2}});
49+
}
50+
4251
TEST_CASE("line geometry", "[NoDB]")
4352
{
4453
geom::geometry_t const geom{geom::linestring_t{{1, 1}, {2, 2}}};
@@ -278,3 +287,30 @@ TEST_CASE("geom::simplify of a loop", "[NoDB]")
278287
REQUIRE(geom.is_null());
279288
}
280289
}
290+
291+
TEST_CASE("geom::simplify of straight line", "[NoDB]")
292+
{
293+
geom::geometry_t const input{geom::linestring_t{{1, 1}, {1, 2}, {1, 3}}};
294+
295+
SECTION("small tolerance simplifies linestring")
296+
{
297+
auto const geom = geom::simplify(input, 0.5);
298+
299+
REQUIRE(geom.is_linestring());
300+
auto const &l = geom.get<geom::linestring_t>();
301+
REQUIRE(l.size() == 2);
302+
REQUIRE(l[0] == input.get<geom::linestring_t>()[0]);
303+
REQUIRE(l[1] == input.get<geom::linestring_t>()[2]);
304+
}
305+
306+
SECTION("large tolerance also simplifies linestring")
307+
{
308+
auto const geom = geom::simplify(input, 10.0);
309+
310+
REQUIRE(geom.is_linestring());
311+
auto const &l = geom.get<geom::linestring_t>();
312+
REQUIRE(l.size() == 2);
313+
REQUIRE(l[0] == input.get<geom::linestring_t>()[0]);
314+
REQUIRE(l[1] == input.get<geom::linestring_t>()[2]);
315+
}
316+
}

tests/test-geom-multilinestrings.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,45 @@ TEST_CASE("create_multilinestring from P shape with closed way", "[NoDB]")
299299
REQUIRE(ml[0] == expected[0]);
300300
REQUIRE(ml[1] == expected[1]);
301301
}
302+
303+
TEST_CASE("create_multilinestring and simplify", "[NoDB]")
304+
{
305+
test_buffer_t buffer;
306+
buffer.add_way("w20 Nn10x1y1,n11x1y2,n12x1y3");
307+
buffer.add_way("w21 Nn12x1y3,n13x2y3,n11x1y2");
308+
309+
auto const geom =
310+
geom::create_multilinestring(buffer.buffer());
311+
312+
REQUIRE(geom.is_multilinestring());
313+
REQUIRE(geom.srid() == 4326);
314+
auto const &mls = geom.get<geom::multilinestring_t>();
315+
REQUIRE(mls.num_geometries() == 2);
316+
REQUIRE(mls[0] == geom::linestring_t{{1, 1}, {1, 2}, {1, 3}});
317+
REQUIRE(mls[1] == geom::linestring_t{{1, 3}, {2, 3}, {1, 2}});
318+
319+
SECTION("simplify with small tolerance")
320+
{
321+
auto const simplified_geom = geom::simplify(geom, 0.1);
322+
REQUIRE(simplified_geom.is_multilinestring());
323+
REQUIRE(simplified_geom.srid() == 4326);
324+
auto const &simplified_mls =
325+
simplified_geom.get<geom::multilinestring_t>();
326+
REQUIRE(simplified_mls.num_geometries() == 2);
327+
REQUIRE(simplified_mls[0] == geom::linestring_t{{1, 1}, {1, 3}});
328+
REQUIRE(simplified_mls[1] ==
329+
geom::linestring_t{{1, 3}, {2, 3}, {1, 2}});
330+
}
331+
332+
SECTION("simplify with large tolerance")
333+
{
334+
auto const simplified_geom = geom::simplify(geom, 10.0);
335+
REQUIRE(simplified_geom.is_multilinestring());
336+
REQUIRE(simplified_geom.srid() == 4326);
337+
auto const &simplified_mls =
338+
simplified_geom.get<geom::multilinestring_t>();
339+
REQUIRE(simplified_mls.num_geometries() == 2);
340+
REQUIRE(simplified_mls[0] == geom::linestring_t{{1, 1}, {1, 3}});
341+
REQUIRE(simplified_mls[1] == geom::linestring_t{{1, 3}, {1, 2}});
342+
}
343+
}

0 commit comments

Comments
 (0)