Skip to content

Commit a51da52

Browse files
authored
Merge pull request #1782 from joto/geom-simplify-multilinestring
Add implementation of simplify function for multilinestrings
2 parents 672b1b0 + 4fc662d commit a51da52

File tree

6 files changed

+150
-24
lines changed

6 files changed

+150
-24
lines changed

src/geom-from-osm.cpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ geometry_t create_point(osmium::Node const &node)
2929
return geometry_t{point_t{node.location()}};
3030
}
3131

32-
static void fill_point_list(point_list_t *list,
32+
/**
33+
* Fill point list with locations from nodes list. Consecutive identical
34+
* locations are collapsed into a single point.
35+
*
36+
* Returns true if the result is a valid linestring, i.e. it has more than
37+
* one point.
38+
*/
39+
static bool fill_point_list(point_list_t *list,
3340
osmium::NodeRefList const &nodes)
3441
{
3542
osmium::Location last{};
@@ -41,16 +48,15 @@ static void fill_point_list(point_list_t *list,
4148
last = loc;
4249
}
4350
}
51+
52+
return list->size() > 1;
4453
}
4554

4655
void create_linestring(geometry_t *geom, osmium::Way const &way)
4756
{
4857
auto &line = geom->set<linestring_t>();
4958

50-
fill_point_list(&line, way.nodes());
51-
52-
// Return nullgeom_t if the line geometry is invalid
53-
if (line.size() <= 1U) {
59+
if (!fill_point_list(&line, way.nodes())) {
5460
geom->reset();
5561
}
5662
}
@@ -134,16 +140,14 @@ void create_multilinestring(geometry_t *geom,
134140
if (ways.size() == 1 && !force_multi) {
135141
auto &line = geom->set<linestring_t>();
136142
auto &way = *ways.begin();
137-
fill_point_list(&line, way.nodes());
138-
if (line.size() < 2U) {
143+
if (!fill_point_list(&line, way.nodes())) {
139144
geom->reset();
140145
}
141146
} else {
142147
auto &multiline = geom->set<multilinestring_t>();
143148
for (auto const &way : ways) {
144149
linestring_t line;
145-
fill_point_list(&line, way.nodes());
146-
if (line.size() >= 2U) {
150+
if (fill_point_list(&line, way.nodes())) {
147151
multiline.add_geometry(std::move(line));
148152
}
149153
}
@@ -228,8 +232,7 @@ void create_collection(geometry_t *geom, osmium::memory::Buffer const &buffer)
228232
auto const &way = static_cast<osmium::Way const &>(obj);
229233
geometry_t item;
230234
auto &line = item.set<linestring_t>();
231-
fill_point_list(&line, way.nodes());
232-
if (line.size() >= 2U) {
235+
if (fill_point_list(&line, way.nodes())) {
233236
collection.add_geometry(std::move(item));
234237
}
235238
}

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)