Skip to content

Commit 6c132c6

Browse files
authored
Merge pull request #1776 from joto/geom-centroid-for-collection
Add centroid() implementation for geometry collection
2 parents a8f169f + 0704699 commit 6c132c6

11 files changed

Lines changed: 125 additions & 10 deletions

src/geom-functions.cpp

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -700,16 +700,55 @@ geometry_t line_merge(geometry_t const &input)
700700
return output;
701701
}
702702

703+
/**
704+
* This helper function is used to calculate centroids of geometry collections.
705+
* It first creates a multi geometry that only contains the geometries of
706+
* dimension N from the input collection. This is done by copying the geometry,
707+
* which isn't very efficient, but hopefully the centroid of a geometry
708+
* collection isn't used very often. This can be optimized if needed.
709+
*
710+
* Then the centroid of this new collection is calculated.
711+
*
712+
* Nested geometry collections are not allowed.
713+
*/
714+
template <std::size_t N, typename T>
715+
static void filtered_centroid(collection_t const &collection, point_t *center)
716+
{
717+
multigeometry_t<T> multi;
718+
for (auto const &geom : collection) {
719+
assert(!geom.is_collection());
720+
if (!geom.is_null() && dimension(geom) == N) {
721+
if (geom.is_multi()) {
722+
for (auto const &sgeom : geom.get<multigeometry_t<T>>()) {
723+
multi.add_geometry() = sgeom;
724+
}
725+
} else {
726+
multi.add_geometry() = geom.get<T>();
727+
}
728+
}
729+
}
730+
boost::geometry::centroid(multi, *center);
731+
}
732+
703733
geometry_t centroid(geometry_t const &geom)
704734
{
705735
geom::geometry_t output{point_t{}, geom.srid()};
706736
auto &center = output.get<point_t>();
707737

708738
geom.visit(overloaded{
709739
[&](geom::nullgeom_t const & /*input*/) { output.reset(); },
710-
[&](geom::collection_t const & /*input*/) {
711-
throw std::runtime_error{
712-
"Centroid of geometry collection not implemented yet"};
740+
[&](geom::collection_t const &input) {
741+
switch (dimension(input)) {
742+
case 0:
743+
filtered_centroid<0, point_t>(input, &center);
744+
break;
745+
case 1:
746+
filtered_centroid<1, linestring_t>(input, &center);
747+
break;
748+
default: // 2
749+
filtered_centroid<2, polygon_t>(input, &center);
750+
break;
751+
}
713752
},
714753
[&](auto const &input) { boost::geometry::centroid(input, center); }});
715754

src/geom.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
#include "geom.hpp"
1111

12+
#include <algorithm>
13+
#include <numeric>
14+
1215
namespace geom {
1316

1417
bool operator==(polygon_t const &a, polygon_t const &b) noexcept
@@ -21,4 +24,18 @@ bool operator!=(polygon_t const &a, polygon_t const &b) noexcept
2124
return !(a == b);
2225
}
2326

27+
std::size_t dimension(collection_t const &geom)
28+
{
29+
return std::accumulate(geom.cbegin(), geom.cend(), 0ULL,
30+
[](std::size_t max, auto const &member) {
31+
return std::max(max, dimension(member));
32+
});
33+
}
34+
35+
std::size_t dimension(geometry_t const &geom)
36+
{
37+
return geom.visit(
38+
overloaded{[&](auto const &input) { return dimension(input); }});
39+
}
40+
2441
} // namespace geom

src/geom.hpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,6 @@ using multilinestring_t = multigeometry_t<linestring_t>;
224224
using multipolygon_t = multigeometry_t<polygon_t>;
225225

226226
class geometry_t;
227-
228227
using collection_t = multigeometry_t<geometry_t>;
229228

230229
/**
@@ -363,6 +362,27 @@ class geometry_t
363362

364363
}; // class geometry_t
365364

365+
inline std::size_t dimension(nullgeom_t) noexcept { return 0; }
366+
inline std::size_t dimension(point_t) noexcept { return 0; }
367+
inline std::size_t dimension(linestring_t) noexcept { return 1; }
368+
inline std::size_t dimension(polygon_t) noexcept { return 2; }
369+
inline std::size_t dimension(multipoint_t) noexcept { return 0; }
370+
inline std::size_t dimension(multilinestring_t) noexcept { return 1; }
371+
inline std::size_t dimension(multipolygon_t) noexcept { return 2; }
372+
373+
std::size_t dimension(collection_t const &geom);
374+
375+
/**
376+
* Return the dimension of this geometry. This is:
377+
*
378+
* 0 - for null and point geometries
379+
* 1 - for (multi)linestring geometries
380+
* 2 - for (multi)polygon geometries
381+
*
382+
* For geometry collections this is the largest dimension of all its members.
383+
*/
384+
std::size_t dimension(geometry_t const &geom);
385+
366386
} // namespace geom
367387

368388
// This magic is used for visiting geometries. For an explanation see for

tests/test-geom-collections.cpp

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,34 @@ TEST_CASE("geometry collection with point", "[NoDB]")
2323
c.add_geometry(geom::geometry_t{geom::point_t{1, 1}});
2424

2525
REQUIRE(geometry_type(geom) == "GEOMETRYCOLLECTION");
26+
REQUIRE(dimension(geom) == 0);
2627
REQUIRE(num_geometries(geom) == 1);
2728
REQUIRE(area(geom) == Approx(0.0));
2829
REQUIRE(length(geom) == Approx(0.0));
29-
REQUIRE_THROWS(centroid(geom));
30+
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1, 1}});
3031
REQUIRE(geometry_n(geom, 1) == geom::geometry_t{geom::point_t{1, 1}});
3132
}
3233

34+
TEST_CASE("geometry collection with multipoint", "[NoDB]")
35+
{
36+
geom::geometry_t mpgeom{geom::multipoint_t{}};
37+
auto &mp = mpgeom.get<geom::multipoint_t>();
38+
mp.add_geometry(geom::point_t{1, 1});
39+
mp.add_geometry(geom::point_t{1, 2});
40+
mp.add_geometry(geom::point_t{2, 1});
41+
mp.add_geometry(geom::point_t{2, 2});
42+
43+
geom::geometry_t geom{geom::collection_t{}};
44+
auto &c = geom.get<geom::collection_t>();
45+
c.add_geometry(std::move(mpgeom));
46+
47+
REQUIRE(geometry_type(geom) == "GEOMETRYCOLLECTION");
48+
REQUIRE(dimension(geom) == 0);
49+
REQUIRE(num_geometries(geom) == 1);
50+
REQUIRE(area(geom) == Approx(0.0));
51+
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1.5, 1.5}});
52+
}
53+
3354
TEST_CASE("geometry collection with several geometries", "[NoDB]")
3455
{
3556
geom::geometry_t geom{geom::collection_t{}};
@@ -40,10 +61,11 @@ TEST_CASE("geometry collection with several geometries", "[NoDB]")
4061
c.add_geometry(geom::geometry_t{geom::point_t{2, 2}});
4162

4263
REQUIRE(geometry_type(geom) == "GEOMETRYCOLLECTION");
64+
REQUIRE(dimension(geom) == 1);
4365
REQUIRE(num_geometries(geom) == 3);
4466
REQUIRE(area(geom) == Approx(0.0));
4567
REQUIRE(length(geom) == Approx(1.41421));
46-
REQUIRE_THROWS(centroid(geom));
68+
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1.5, 1.5}});
4769
REQUIRE(geometry_n(geom, 1) == geom::geometry_t{geom::point_t{1, 1}});
4870
REQUIRE(geometry_n(geom, 2) ==
4971
geom::geometry_t{geom::linestring_t{{1, 1}, {2, 2}}});
@@ -55,23 +77,24 @@ TEST_CASE("create_collection from OSM data", "[NoDB]")
5577
test_buffer_t buffer;
5678
buffer.add_node("n1 x1 y1");
5779
buffer.add_way("w20 Nn1x1y1,n2x2y1,n3x2y2,n4x1y2,n1x1y1");
58-
buffer.add_way("w21 Nn5x10y10,n6x10y20");
80+
buffer.add_way("w21 Nn5x10y10,n6x10y11");
5981
buffer.add_relation("r30 Mw20@");
6082

6183
auto const geom = geom::create_collection(buffer.buffer());
6284

6385
REQUIRE(geometry_type(geom) == "GEOMETRYCOLLECTION");
86+
REQUIRE(dimension(geom) == 1);
6487
REQUIRE(num_geometries(geom) == 3);
6588

6689
auto const &c = geom.get<geom::collection_t>();
6790
REQUIRE(c[0] == geom::geometry_t{geom::point_t{1, 1}});
6891
REQUIRE(c[1] == geom::geometry_t{geom::linestring_t{
6992
{1, 1}, {2, 1}, {2, 2}, {1, 2}, {1, 1}}});
70-
REQUIRE(c[2] == geom::geometry_t{geom::linestring_t{{10, 10}, {10, 20}}});
93+
REQUIRE(c[2] == geom::geometry_t{geom::linestring_t{{10, 10}, {10, 11}}});
7194

7295
REQUIRE(area(geom) == Approx(0.0));
73-
REQUIRE(length(geom) == Approx(14.0));
74-
REQUIRE_THROWS(centroid(geom));
96+
REQUIRE(length(geom) == Approx(5.0));
97+
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{3.2, 3.3}});
7598
}
7699

77100
TEST_CASE("create_collection from no OSM data returns null geometry", "[NoDB]")
@@ -82,6 +105,7 @@ TEST_CASE("create_collection from no OSM data returns null geometry", "[NoDB]")
82105
auto const geom = geom::create_collection(buffer.buffer());
83106

84107
REQUIRE(geometry_type(geom) == "NULL");
108+
REQUIRE(dimension(geom) == 0);
85109
REQUIRE(num_geometries(geom) == 0);
86110
}
87111

tests/test-geom-linestrings.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ TEST_CASE("line geometry", "[NoDB]")
4242
{
4343
geom::geometry_t const geom{geom::linestring_t{{1, 1}, {2, 2}}};
4444

45+
REQUIRE(dimension(geom) == 1);
4546
REQUIRE(num_geometries(geom) == 1);
4647
REQUIRE(area(geom) == Approx(0.0));
4748
REQUIRE(length(geom) == Approx(1.41421));
@@ -73,6 +74,7 @@ TEST_CASE("create_linestring from OSM data", "[NoDB]")
7374

7475
REQUIRE(geom.is_linestring());
7576
REQUIRE(geometry_type(geom) == "LINESTRING");
77+
REQUIRE(dimension(geom) == 1);
7678
REQUIRE(num_geometries(geom) == 1);
7779
REQUIRE(area(geom) == Approx(0.0));
7880
REQUIRE(length(geom) == Approx(1.41421));

tests/test-geom-multilinestrings.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ TEST_CASE("create_multilinestring with single line", "[NoDB]")
3030

3131
REQUIRE(geom.is_multilinestring());
3232
REQUIRE(geometry_type(geom) == "MULTILINESTRING");
33+
REQUIRE(dimension(geom) == 1);
3334
REQUIRE(num_geometries(geom) == 1);
3435
REQUIRE(area(geom) == Approx(0.0));
3536
REQUIRE(length(geom) == Approx(1.0));
@@ -74,6 +75,7 @@ TEST_CASE("create_multilinestring with single line forming a ring", "[NoDB]")
7475
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
7576

7677
REQUIRE(geom.is_multilinestring());
78+
REQUIRE(dimension(geom) == 1);
7779
auto const &ml = geom.get<geom::multilinestring_t>();
7880
REQUIRE(ml.num_geometries() == 1);
7981
REQUIRE(ml[0] == expected);
@@ -92,6 +94,7 @@ TEST_CASE("create_multilinestring from two non-joined lines", "[NoDB]")
9294
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
9395

9496
REQUIRE(geom.is_multilinestring());
97+
REQUIRE(dimension(geom) == 1);
9598
auto const &ml = geom.get<geom::multilinestring_t>();
9699
REQUIRE(ml.num_geometries() == 2);
97100
REQUIRE(ml[0] == expected[0]);

tests/test-geom-multipoints.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ TEST_CASE("multipoint_t with a single point", "[NoDB]")
2828

2929
REQUIRE(geom.is_multipoint());
3030
REQUIRE(geometry_type(geom) == "MULTIPOINT");
31+
REQUIRE(dimension(geom) == 0);
3132
REQUIRE(num_geometries(geom) == 1);
3233
REQUIRE(area(geom) == Approx(0.0));
3334
REQUIRE(length(geom) == Approx(0.0));

tests/test-geom-multipolygons.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ TEST_CASE("multipolygon geometry with single outer, no inner", "[NoDB]")
2424
geom::polygon_t{geom::ring_t{{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}});
2525

2626
REQUIRE(geometry_type(geom) == "MULTIPOLYGON");
27+
REQUIRE(dimension(geom) == 2);
2728
REQUIRE(num_geometries(geom) == 1);
2829
REQUIRE(area(geom) == Approx(1.0));
2930
REQUIRE(length(geom) == Approx(0.0));
@@ -51,6 +52,7 @@ TEST_CASE("multipolygon geometry with two polygons", "[NoDB]")
5152
mp.add_geometry(std::move(polygon));
5253

5354
REQUIRE(geometry_type(geom) == "MULTIPOLYGON");
55+
REQUIRE(dimension(geom) == 2);
5456
REQUIRE(num_geometries(geom) == 2);
5557
REQUIRE(area(geom) == Approx(9.0));
5658
REQUIRE(length(geom) == Approx(0.0));
@@ -67,6 +69,7 @@ TEST_CASE("create_multipolygon creates simple polygon from OSM data", "[NoDB]")
6769

6870
REQUIRE(geom.is_polygon());
6971
REQUIRE(geometry_type(geom) == "POLYGON");
72+
REQUIRE(dimension(geom) == 2);
7073
REQUIRE(num_geometries(geom) == 1);
7174
REQUIRE(area(geom) == Approx(1.0));
7275
REQUIRE(length(geom) == Approx(0.0));

tests/test-geom-null.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ TEST_CASE("null geometry", "[NoDB]")
1717
{
1818
geom::geometry_t const geom{};
1919

20+
REQUIRE(dimension(geom) == 0);
2021
REQUIRE(num_geometries(geom) == 0);
2122
REQUIRE(area(geom) == 0.0);
2223
REQUIRE(length(geom) == Approx(0.0));

tests/test-geom-points.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ TEST_CASE("create_point from OSM data", "[NoDB]")
5050

5151
REQUIRE(geom.is_point());
5252
REQUIRE(geometry_type(geom) == "POINT");
53+
REQUIRE(dimension(geom) == 0);
5354
REQUIRE(num_geometries(geom) == 1);
5455
REQUIRE(area(geom) == Approx(0.0));
5556
REQUIRE(length(geom) == Approx(0.0));

0 commit comments

Comments
 (0)