Skip to content

Commit 57875ca

Browse files
authored
Merge pull request #2019 from joto/spherical-area
Add spherical_area() function
2 parents 7f7c9a5 + 97d8f32 commit 57875ca

File tree

6 files changed

+88
-2
lines changed

6 files changed

+88
-2
lines changed

flex-config/geometries.lua

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ tables.polygons = osm2pgsql.define_area_table('polygons', {
3434
-- if we have multiple geometry columns and some of them can be valid
3535
-- and others not.
3636
{ column = 'geom', type = 'geometry', projection = 4326 },
37+
-- In this column we'll put the area calculated in Mercator coordinates
3738
{ column = 'area', type = 'real' },
39+
-- In this column we'll put the true area calculated on the spheroid
40+
{ column = 'spherical_area', type = 'real' },
3841
})
3942

4043
tables.boundaries = osm2pgsql.define_relation_table('boundaries', {
@@ -135,7 +138,9 @@ function osm2pgsql.process_way(object)
135138
geom = geom,
136139
-- Calculate the area in Mercator projection and store in the
137140
-- area column
138-
area = geom:transform(3857):area()
141+
area = geom:transform(3857):area(),
142+
-- Also calculate "real" area in spheroid
143+
spherical_area = geom:spherical_area()
139144
})
140145
else
141146
-- We want to split long lines into smaller segments. We can use
@@ -192,7 +197,9 @@ function osm2pgsql.process_relation(object)
192197
geom = geom,
193198
-- Calculate the area in Mercator projection and store in the
194199
-- area column
195-
area = geom:transform(3857):area()
200+
area = geom:transform(3857):area(),
201+
-- Also calculate "real" area in spheroid
202+
spherical_area = geom:spherical_area()
196203
})
197204
end
198205
end

src/flex-lua-geom.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ static int geom_area(lua_State *lua_state)
6666
return 1;
6767
}
6868

69+
static int geom_spherical_area(lua_State *lua_state)
70+
{
71+
auto const *const input_geometry = unpack_geometry(lua_state);
72+
73+
if (input_geometry->srid() != 4326) {
74+
throw std::runtime_error{"Can only calculate spherical area for "
75+
"geometries in WGS84 (4326) coordinates."};
76+
}
77+
78+
try {
79+
lua_pushnumber(lua_state, geom::spherical_area(*input_geometry));
80+
} catch (...) {
81+
return luaL_error(lua_state, "Unknown error in 'spherical_area()'.\n");
82+
}
83+
84+
return 1;
85+
}
86+
6987
static int geom_length(lua_State *lua_state)
7088
{
7189
auto const *const input_geometry = unpack_geometry(lua_state);
@@ -286,6 +304,7 @@ void init_geometry_class(lua_State *lua_state)
286304
geom_pole_of_inaccessibility);
287305
luaX_add_table_func(lua_state, "segmentize", geom_segmentize);
288306
luaX_add_table_func(lua_state, "simplify", geom_simplify);
307+
luaX_add_table_func(lua_state, "spherical_area", geom_spherical_area);
289308
luaX_add_table_func(lua_state, "srid", geom_srid);
290309
luaX_add_table_func(lua_state, "transform", geom_transform);
291310

src/geom-functions.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,44 @@ double area(geometry_t const &geom)
364364

365365
/****************************************************************************/
366366

367+
static double spherical_area(polygon_t const &geom)
368+
{
369+
boost::geometry::strategy::area::spherical<> const spherical_earth{
370+
6371008.8};
371+
372+
using sph_point = boost::geometry::model::point<
373+
double, 2,
374+
boost::geometry::cs::spherical_equatorial<boost::geometry::degree>>;
375+
376+
boost::geometry::model::polygon<sph_point> sph_geom;
377+
boost::geometry::convert(geom, sph_geom);
378+
return boost::geometry::area(sph_geom, spherical_earth);
379+
}
380+
381+
double spherical_area(geometry_t const &geom)
382+
{
383+
assert(geom.srid() == 4326);
384+
385+
return std::abs(geom.visit(overloaded{
386+
[&](geom::nullgeom_t const & /*input*/) { return 0.0; },
387+
[&](geom::collection_t const &input) {
388+
return std::accumulate(input.cbegin(), input.cend(), 0.0,
389+
[](double sum, auto const &geom) {
390+
return sum + spherical_area(geom);
391+
});
392+
},
393+
[&](geom::polygon_t const &input) { return spherical_area(input); },
394+
[&](geom::multipolygon_t const &input) {
395+
return std::accumulate(input.cbegin(), input.cend(), 0.0,
396+
[&](double sum, auto const &geom) {
397+
return sum + spherical_area(geom);
398+
});
399+
},
400+
[&](auto const & /*input*/) { return 0.0; }}));
401+
}
402+
403+
/****************************************************************************/
404+
367405
double length(geometry_t const &geom)
368406
{
369407
return geom.visit(overloaded{

src/geom-functions.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,17 @@ geometry_t segmentize(geometry_t const &input, double max_segment_length);
147147
*/
148148
double area(geometry_t const &geom);
149149

150+
/**
151+
* Calculate area of geometry on the spheroid.
152+
* For geometry types other than polygon or multipolygon this will always
153+
* return 0.
154+
*
155+
* \param geom Input geometry.
156+
* \returns Area in m².
157+
* \pre \code geom.srid() == 4326 \endcode
158+
*/
159+
double spherical_area(geometry_t const &geom);
160+
150161
/**
151162
* Split multigeometries into their parts. Non-multi geometries are left
152163
* alone and will end up as the only geometry in the result vector. If the

tests/test-geom-multipolygons.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ TEST_CASE("multipolygon geometry with single outer, no inner", "[NoDB]")
2121
geom::geometry_t geom{geom::multipolygon_t{}};
2222
auto &mp = geom.get<geom::multipolygon_t>();
2323

24+
// MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))
2425
mp.add_geometry(
2526
geom::polygon_t{geom::ring_t{{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}});
2627

2728
REQUIRE(geometry_type(geom) == "MULTIPOLYGON");
2829
REQUIRE(dimension(geom) == 2);
2930
REQUIRE(num_geometries(geom) == 1);
3031
REQUIRE(area(geom) == Approx(1.0));
32+
REQUIRE(spherical_area(geom) == Approx(12364031798.5));
3133
REQUIRE(length(geom) == Approx(0.0));
3234
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{0.5, 0.5}});
3335
REQUIRE(geometry_n(geom, 1) ==
@@ -40,6 +42,7 @@ TEST_CASE("multipolygon geometry with two polygons", "[NoDB]")
4042
geom::geometry_t geom{geom::multipolygon_t{}};
4143
auto &mp = geom.get<geom::multipolygon_t>();
4244

45+
// MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 5, 5 5, 5 2, 2 2), (3 3, 4 3, 4 4, 3 4, 3 3)))
4346
mp.add_geometry(
4447
geom::polygon_t{geom::ring_t{{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}});
4548

@@ -56,6 +59,7 @@ TEST_CASE("multipolygon geometry with two polygons", "[NoDB]")
5659
REQUIRE(dimension(geom) == 2);
5760
REQUIRE(num_geometries(geom) == 2);
5861
REQUIRE(area(geom) == Approx(9.0));
62+
REQUIRE(spherical_area(geom) == Approx(111106540105.7));
5963
REQUIRE(length(geom) == Approx(0.0));
6064
}
6165

tests/test-geom-polygons.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818

1919
TEST_CASE("polygon geometry without inner", "[NoDB]")
2020
{
21+
// POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))
2122
geom::geometry_t const geom{
2223
geom::polygon_t{geom::ring_t{{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}}};
2324

2425
REQUIRE(dimension(geom) == 2);
2526
REQUIRE(num_geometries(geom) == 1);
2627
REQUIRE(area(geom) == Approx(1.0));
28+
REQUIRE(spherical_area(geom) == Approx(12364031798.5));
2729
REQUIRE(length(geom) == Approx(0.0));
2830
REQUIRE(geometry_type(geom) == "POLYGON");
2931
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{0.5, 0.5}});
@@ -32,12 +34,14 @@ TEST_CASE("polygon geometry without inner", "[NoDB]")
3234

3335
TEST_CASE("polygon geometry without inner (reverse)", "[NoDB]")
3436
{
37+
// POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))
3538
geom::geometry_t const geom{
3639
geom::polygon_t{geom::ring_t{{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}}}};
3740

3841
REQUIRE(dimension(geom) == 2);
3942
REQUIRE(num_geometries(geom) == 1);
4043
REQUIRE(area(geom) == Approx(1.0));
44+
REQUIRE(spherical_area(geom) == Approx(12364031798.5));
4145
REQUIRE(length(geom) == Approx(0.0));
4246
REQUIRE(geometry_type(geom) == "POLYGON");
4347
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{0.5, 0.5}});
@@ -48,6 +52,8 @@ TEST_CASE("geom::polygon_t", "[NoDB]")
4852
geom::polygon_t polygon;
4953

5054
REQUIRE(polygon.outer().empty());
55+
56+
// POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))
5157
polygon.outer() = geom::ring_t{{0, 0}, {0, 3}, {3, 3}, {3, 0}, {0, 0}};
5258
polygon.inners().emplace_back(
5359
geom::ring_t{{1, 1}, {2, 1}, {2, 2}, {1, 2}, {1, 1}});
@@ -59,6 +65,7 @@ TEST_CASE("geom::polygon_t", "[NoDB]")
5965
REQUIRE(dimension(geom) == 2);
6066
REQUIRE(num_geometries(geom) == 1);
6167
REQUIRE(area(geom) == Approx(8.0));
68+
REQUIRE(spherical_area(geom) == Approx(98893356298.4));
6269
REQUIRE(length(geom) == Approx(0.0));
6370
REQUIRE(geometry_type(geom) == "POLYGON");
6471
REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1.5, 1.5}});

0 commit comments

Comments
 (0)