From f0aac2bbbc8e7b60268c35f00ce9da8a752dcfb1 Mon Sep 17 00:00:00 2001 From: Jochen Topf Date: Sun, 17 May 2026 21:06:45 +0200 Subject: [PATCH 1/2] Add n_points() member function to all geometry types Returns the number of points in a geometry. --- src/geom.cpp | 19 ++++++++++++++++++ src/geom.hpp | 29 ++++++++++++++++++++++++++++ tests/test-geom-collections.cpp | 5 +++++ tests/test-geom-linestrings.cpp | 1 + tests/test-geom-multilinestrings.cpp | 5 +++++ tests/test-geom-multipoints.cpp | 3 +++ tests/test-geom-multipolygons.cpp | 4 ++++ tests/test-geom-null.cpp | 1 + tests/test-geom-points.cpp | 1 + tests/test-geom-polygons.cpp | 3 +++ 10 files changed, 71 insertions(+) diff --git a/src/geom.cpp b/src/geom.cpp index 952e0fe85..234387b0e 100644 --- a/src/geom.cpp +++ b/src/geom.cpp @@ -30,6 +30,25 @@ bool operator!=(polygon_t const &a, polygon_t const &b) noexcept return !(a == b); } +[[nodiscard]] std::size_t polygon_t::n_points() const +{ + return m_outer.size() + + std::accumulate(m_inners.cbegin(), m_inners.cend(), std::size_t{0}, + [](std::size_t sum, ring_t const &ring) { + return sum + ring.size(); + }); +} + +[[nodiscard]] std::size_t geometry_t::n_points() const +{ + return visit([](auto const &input) { return input.n_points(); }); +} + +std::size_t n_points(geometry_t const &geom) +{ + return geom.visit([](auto const &input) { return input.n_points(); }); +} + std::size_t dimension(collection_t const &geom) { return std::accumulate(geom.cbegin(), geom.cend(), 0ULL, diff --git a/src/geom.hpp b/src/geom.hpp index 6cf1ae219..049e56339 100644 --- a/src/geom.hpp +++ b/src/geom.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,11 @@ class nullgeom_t return 0; } + [[nodiscard]] constexpr static std::size_t n_points() noexcept + { + return 0; + } + [[nodiscard]] constexpr friend bool operator==(nullgeom_t, nullgeom_t) noexcept { @@ -69,6 +75,11 @@ class point_t return 1; } + [[nodiscard]] constexpr static std::size_t n_points() noexcept + { + return 1; + } + [[nodiscard]] constexpr double x() const noexcept { return m_x; } [[nodiscard]] constexpr double y() const noexcept { return m_y; } @@ -148,6 +159,11 @@ class linestring_t : public point_list_t return 1; } + [[nodiscard]] std::size_t n_points() const noexcept + { + return size(); + } + }; // class linestring_t class ring_t : public point_list_t @@ -186,6 +202,8 @@ class polygon_t friend bool operator!=(polygon_t const &a, polygon_t const &b) noexcept; + [[nodiscard]] std::size_t n_points() const; + private: ring_t m_outer; std::vector m_inners; @@ -257,6 +275,15 @@ class multigeometry_t void reserve(std::size_t size) { m_geometry.reserve(size); } + [[nodiscard]] std::size_t n_points() const + { + return std::accumulate(m_geometry.cbegin(), m_geometry.cend(), + std::size_t{0}, + [](std::size_t sum, auto const &geom) { + return sum + geom.n_points(); + }); + } + private: std::vector m_geometry; @@ -400,6 +427,8 @@ class geometry_t return !(a == b); } + [[nodiscard]] std::size_t n_points() const; + private: std::variant diff --git a/tests/test-geom-collections.cpp b/tests/test-geom-collections.cpp index d7b14bf3a..0ce5ef221 100644 --- a/tests/test-geom-collections.cpp +++ b/tests/test-geom-collections.cpp @@ -24,6 +24,7 @@ TEST_CASE("geometry collection with point", "[NoDB]") c.add_geometry(geom::geometry_t{geom::point_t{1, 1}}); REQUIRE(geometry_type(geom) == "GEOMETRYCOLLECTION"); + REQUIRE(geom.n_points() == 1); REQUIRE(dimension(geom) == 0); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(0.0)); @@ -46,6 +47,7 @@ TEST_CASE("geometry collection with multipoint", "[NoDB]") c.add_geometry(std::move(mpgeom)); REQUIRE(geometry_type(geom) == "GEOMETRYCOLLECTION"); + REQUIRE(geom.n_points() == 4); REQUIRE(dimension(geom) == 0); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(0.0)); @@ -62,6 +64,7 @@ TEST_CASE("geometry collection with several geometries", "[NoDB]") c.add_geometry(geom::geometry_t{geom::point_t{2, 2}}); REQUIRE(geometry_type(geom) == "GEOMETRYCOLLECTION"); + REQUIRE(geom.n_points() == 4); REQUIRE(dimension(geom) == 1); REQUIRE(num_geometries(geom) == 3); REQUIRE(area(geom) == Approx(0.0)); @@ -83,6 +86,7 @@ TEST_CASE("geometry collection with polygon", "[NoDB]") geom::polygon_t{geom::ring_t{{1, 1}, {1, 2}, {2, 2}, {2, 1}, {1, 1}}}}); REQUIRE(geometry_type(geom) == "GEOMETRYCOLLECTION"); + REQUIRE(geom.n_points() == 6); REQUIRE(num_geometries(geom) == 2); REQUIRE(area(geom) == Approx(1.0)); REQUIRE(length(geom) == Approx(0.0)); @@ -100,6 +104,7 @@ TEST_CASE("create_collection from OSM data", "[NoDB]") auto const geom = geom::create_collection(buffer.buffer()); REQUIRE(geometry_type(geom) == "GEOMETRYCOLLECTION"); + REQUIRE(geom.n_points() == 8); REQUIRE(dimension(geom) == 1); REQUIRE(num_geometries(geom) == 3); diff --git a/tests/test-geom-linestrings.cpp b/tests/test-geom-linestrings.cpp index 64767d222..630af4d7e 100644 --- a/tests/test-geom-linestrings.cpp +++ b/tests/test-geom-linestrings.cpp @@ -52,6 +52,7 @@ TEST_CASE("line geometry", "[NoDB]") { geom::geometry_t const geom{geom::linestring_t{{1, 1}, {2, 2}}}; + REQUIRE(geom.n_points() == 2); REQUIRE(dimension(geom) == 1); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(0.0)); diff --git a/tests/test-geom-multilinestrings.cpp b/tests/test-geom-multilinestrings.cpp index a91fb5888..25a9c2f03 100644 --- a/tests/test-geom-multilinestrings.cpp +++ b/tests/test-geom-multilinestrings.cpp @@ -32,6 +32,7 @@ TEST_CASE("create_multilinestring with single line", "[NoDB]") REQUIRE(geom.is_multilinestring()); REQUIRE(geometry_type(geom) == "MULTILINESTRING"); + REQUIRE(geom.n_points() == 2); REQUIRE(dimension(geom) == 1); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(0.0)); @@ -59,6 +60,7 @@ TEST_CASE("create_multilinestring with single line and no force_multi", REQUIRE(geom.is_linestring()); REQUIRE(geometry_type(geom) == "LINESTRING"); + REQUIRE(geom.n_points() == 2); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(0.0)); REQUIRE(spherical_area(geom) == Approx(0.0)); @@ -132,6 +134,7 @@ TEST_CASE("create_multilinestring from two non-joined lines", "[NoDB]") geom::line_merge(geom::create_multilinestring(buffer.buffer())); REQUIRE(geom.is_multilinestring()); + REQUIRE(geom.n_points() == 4); REQUIRE(dimension(geom) == 1); auto const &ml = geom.get(); REQUIRE(ml.num_geometries() == 2); @@ -152,6 +155,7 @@ TEST_CASE("create_multilinestring from two lines end to end", "[NoDB]") REQUIRE(geom.is_multilinestring()); auto const &ml = geom.get(); + REQUIRE(ml.n_points() == 3); REQUIRE(ml.num_geometries() == 1); REQUIRE(ml[0] == expected); } @@ -292,6 +296,7 @@ TEST_CASE("create_multilinestring from Y shape", "[NoDB]") REQUIRE(geom.is_multilinestring()); auto const &ml = geom.get(); REQUIRE(ml.num_geometries() == 2); + REQUIRE(ml.n_points() == 5); REQUIRE(ml[0] == expected[0]); REQUIRE(ml[1] == expected[1]); } diff --git a/tests/test-geom-multipoints.cpp b/tests/test-geom-multipoints.cpp index 7549b926e..b85d55df8 100644 --- a/tests/test-geom-multipoints.cpp +++ b/tests/test-geom-multipoints.cpp @@ -29,6 +29,7 @@ TEST_CASE("multipoint_t with a single point", "[NoDB]") REQUIRE(geom.is_multipoint()); REQUIRE(geometry_type(geom) == "MULTIPOINT"); + REQUIRE(geom.n_points() == 1); REQUIRE(dimension(geom) == 0); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(0.0)); @@ -54,6 +55,7 @@ TEST_CASE("multipoint_t with several points", "[NoDB]") REQUIRE(geom.is_multipoint()); REQUIRE(geometry_type(geom) == "MULTIPOINT"); + REQUIRE(geom.n_points() == 3); REQUIRE(num_geometries(geom) == 3); REQUIRE(area(geom) == Approx(0.0)); REQUIRE(spherical_area(geom) == Approx(0.0)); @@ -85,6 +87,7 @@ TEST_CASE("create_multipoint from OSM data", "[NoDB]") REQUIRE(geometry_type(geom) == "MULTIPOINT"); REQUIRE(dimension(geom) == 0); + REQUIRE(geom.n_points() == 4); REQUIRE(num_geometries(geom) == 4); auto const &c = geom.get(); diff --git a/tests/test-geom-multipolygons.cpp b/tests/test-geom-multipolygons.cpp index 7f834b080..3a6a7534f 100644 --- a/tests/test-geom-multipolygons.cpp +++ b/tests/test-geom-multipolygons.cpp @@ -26,6 +26,7 @@ TEST_CASE("multipolygon geometry with single outer, no inner", "[NoDB]") geom::polygon_t{geom::ring_t{{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}}); REQUIRE(geometry_type(geom) == "MULTIPOLYGON"); + REQUIRE(geom.n_points() == 5); REQUIRE(dimension(geom) == 2); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(1.0)); @@ -56,6 +57,7 @@ TEST_CASE("multipolygon geometry with two polygons", "[NoDB]") mp.add_geometry(std::move(polygon)); REQUIRE(geometry_type(geom) == "MULTIPOLYGON"); + REQUIRE(geom.n_points() == 15); REQUIRE(dimension(geom) == 2); REQUIRE(num_geometries(geom) == 2); REQUIRE(area(geom) == Approx(9.0)); @@ -76,6 +78,7 @@ TEST_CASE("create_multipolygon creates simple polygon from OSM data", "[NoDB]") REQUIRE(geom.is_polygon()); REQUIRE(geometry_type(geom) == "POLYGON"); + REQUIRE(geom.n_points() == 5); REQUIRE(dimension(geom) == 2); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(1.0)); @@ -99,6 +102,7 @@ TEST_CASE("create_multipolygon from OSM data", "[NoDB]") geom::create_multipolygon(relation, buffer.buffer(), &area_buffer); REQUIRE(geom.is_multipolygon()); + REQUIRE(geom.n_points() == 9); REQUIRE(geometry_type(geom) == "MULTIPOLYGON"); REQUIRE(num_geometries(geom) == 2); REQUIRE(area(geom) == Approx(51.0)); diff --git a/tests/test-geom-null.cpp b/tests/test-geom-null.cpp index 1e3014e37..545c3efc4 100644 --- a/tests/test-geom-null.cpp +++ b/tests/test-geom-null.cpp @@ -18,6 +18,7 @@ TEST_CASE("null geometry", "[NoDB]") { geom::geometry_t const geom{}; + REQUIRE(geom.n_points() == 0); REQUIRE(dimension(geom) == 0); REQUIRE(num_geometries(geom) == 0); REQUIRE(area(geom) == Approx(0.0)); diff --git a/tests/test-geom-points.cpp b/tests/test-geom-points.cpp index 5e80025e6..8e6d5de5c 100644 --- a/tests/test-geom-points.cpp +++ b/tests/test-geom-points.cpp @@ -65,6 +65,7 @@ TEST_CASE("create_point from OSM data", "[NoDB]") REQUIRE(geom.is_point()); REQUIRE(geometry_type(geom) == "POINT"); + REQUIRE(geom.n_points() == 1); REQUIRE(dimension(geom) == 0); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(0.0)); diff --git a/tests/test-geom-polygons.cpp b/tests/test-geom-polygons.cpp index 70eb64113..58622c467 100644 --- a/tests/test-geom-polygons.cpp +++ b/tests/test-geom-polygons.cpp @@ -22,6 +22,7 @@ TEST_CASE("polygon geometry without inner", "[NoDB]") geom::geometry_t const geom{ geom::polygon_t{geom::ring_t{{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}}}; + REQUIRE(geom.n_points() == 5); REQUIRE(dimension(geom) == 2); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(1.0)); @@ -38,6 +39,7 @@ TEST_CASE("polygon geometry without inner (reverse)", "[NoDB]") geom::geometry_t const geom{ geom::polygon_t{geom::ring_t{{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}}}}; + REQUIRE(geom.n_points() == 5); REQUIRE(dimension(geom) == 2); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(1.0)); @@ -62,6 +64,7 @@ TEST_CASE("geom::polygon_t", "[NoDB]") REQUIRE(polygon.inners().size() == 1); geom::geometry_t const geom{std::move(polygon)}; + REQUIRE(geom.n_points() == 10); REQUIRE(dimension(geom) == 2); REQUIRE(num_geometries(geom) == 1); REQUIRE(area(geom) == Approx(8.0)); From 916e78b81d59f14050f5fec45ec343031dfa82f6 Mon Sep 17 00:00:00 2001 From: Jochen Topf Date: Sun, 17 May 2026 21:22:50 +0200 Subject: [PATCH 2/2] Make n_points function for geometries available in Lua --- src/flex-lua-geom.cpp | 15 +++++++++++++++ tests/bdd/flex/geometry-linestring.feature | 10 ++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/flex-lua-geom.cpp b/src/flex-lua-geom.cpp index 8592a60c2..6bfee5293 100644 --- a/src/flex-lua-geom.cpp +++ b/src/flex-lua-geom.cpp @@ -114,6 +114,20 @@ int geom_centroid(lua_State *lua_state) return 1; } +int geom_n_points(lua_State *lua_state) +{ + auto const *const input_geometry = unpack_geometry(lua_state); + + try { + lua_pushinteger(lua_state, + static_cast(input_geometry->n_points())); + } catch (...) { + return luaL_error(lua_state, "Unknown error in 'n_points()'.\n"); + } + + return 1; +} + int geom_geometry_n(lua_State *lua_state) { auto const *const input_geometry = unpack_geometry(lua_state); @@ -313,6 +327,7 @@ void init_geometry_class(lua_State *lua_state) {"geometry_type", geom_geometry_type}, {"is_null", geom_is_null}, {"line_merge", geom_line_merge}, + {"n_points", geom_n_points}, {"reverse", geom_reverse}, {"num_geometries", geom_num_geometries}, {"pole_of_inaccessibility", geom_pole_of_inaccessibility}, diff --git a/tests/bdd/flex/geometry-linestring.feature b/tests/bdd/flex/geometry-linestring.feature index 6bd2f0d5e..71c4dc972 100644 --- a/tests/bdd/flex/geometry-linestring.feature +++ b/tests/bdd/flex/geometry-linestring.feature @@ -16,6 +16,7 @@ Feature: Creating linestring features from way { column = 'sgeom', type = 'linestring', projection = 4326 }, { column = 'mgeom', type = 'multilinestring', projection = 4326 }, { column = 'xgeom', type = 'multilinestring', projection = 4326 }, + { column = 'npoints', type = 'int' }, }) function osm2pgsql.process_way(object) @@ -23,7 +24,8 @@ Feature: Creating linestring features from way lines:insert({ sgeom = object:as_linestring(), mgeom = object:as_multilinestring(), - xgeom = object:as_linestring() + xgeom = object:as_linestring(), + npoints = object:as_linestring():n_points(), }) end end @@ -32,9 +34,9 @@ Feature: Creating linestring features from way When running osm2pgsql flex Then table osm2pgsql_test_lines contains exactly - | way_id | sgeom!geo | mgeom!geo | xgeom!geo | - | 20 | 1, 2, 3 | [ 1, 2, 3 ] | [ 1, 2, 3 ] | - | 21 | 4, 5 | [ 4, 5 ] | [ 4, 5 ] | + | way_id | sgeom!geo | mgeom!geo | xgeom!geo | npoints | + | 20 | 1, 2, 3 | [ 1, 2, 3 ] | [ 1, 2, 3 ] | 3 | + | 21 | 4, 5 | [ 4, 5 ] | [ 4, 5 ] | 2 | Scenario: Given the grid