Skip to content

Commit 0704699

Browse files
committed
Add support for centroid() on geometry collections
The implementation isn't really efficient, because it needs to copy (part of) the geometry collection, but it can be improved later on if needed.
1 parent 0e76254 commit 0704699

File tree

2 files changed

+68
-9
lines changed

2 files changed

+68
-9
lines changed

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

tests/test-geom-collections.cpp

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,30 @@ TEST_CASE("geometry collection with point", "[NoDB]")
2727
REQUIRE(num_geometries(geom) == 1);
2828
REQUIRE(area(geom) == Approx(0.0));
2929
REQUIRE(length(geom) == Approx(0.0));
30-
REQUIRE_THROWS(centroid(geom));
30+
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1, 1}});
3131
REQUIRE(geometry_n(geom, 1) == geom::geometry_t{geom::point_t{1, 1}});
3232
}
3333

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+
3454
TEST_CASE("geometry collection with several geometries", "[NoDB]")
3555
{
3656
geom::geometry_t geom{geom::collection_t{}};
@@ -45,7 +65,7 @@ TEST_CASE("geometry collection with several geometries", "[NoDB]")
4565
REQUIRE(num_geometries(geom) == 3);
4666
REQUIRE(area(geom) == Approx(0.0));
4767
REQUIRE(length(geom) == Approx(1.41421));
48-
REQUIRE_THROWS(centroid(geom));
68+
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1.5, 1.5}});
4969
REQUIRE(geometry_n(geom, 1) == geom::geometry_t{geom::point_t{1, 1}});
5070
REQUIRE(geometry_n(geom, 2) ==
5171
geom::geometry_t{geom::linestring_t{{1, 1}, {2, 2}}});
@@ -57,7 +77,7 @@ TEST_CASE("create_collection from OSM data", "[NoDB]")
5777
test_buffer_t buffer;
5878
buffer.add_node("n1 x1 y1");
5979
buffer.add_way("w20 Nn1x1y1,n2x2y1,n3x2y2,n4x1y2,n1x1y1");
60-
buffer.add_way("w21 Nn5x10y10,n6x10y20");
80+
buffer.add_way("w21 Nn5x10y10,n6x10y11");
6181
buffer.add_relation("r30 Mw20@");
6282

6383
auto const geom = geom::create_collection(buffer.buffer());
@@ -70,11 +90,11 @@ TEST_CASE("create_collection from OSM data", "[NoDB]")
7090
REQUIRE(c[0] == geom::geometry_t{geom::point_t{1, 1}});
7191
REQUIRE(c[1] == geom::geometry_t{geom::linestring_t{
7292
{1, 1}, {2, 1}, {2, 2}, {1, 2}, {1, 1}}});
73-
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}}});
7494

7595
REQUIRE(area(geom) == Approx(0.0));
76-
REQUIRE(length(geom) == Approx(14.0));
77-
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}});
7898
}
7999

80100
TEST_CASE("create_collection from no OSM data returns null geometry", "[NoDB]")

0 commit comments

Comments
 (0)