diff --git a/.github/actions/win-install/action.yml b/.github/actions/win-install/action.yml index 0e37dd4b3..bf5171099 100644 --- a/.github/actions/win-install/action.yml +++ b/.github/actions/win-install/action.yml @@ -5,7 +5,7 @@ runs: steps: - name: Install packages - run: vcpkg install bzip2:x64-windows expat:x64-windows zlib:x64-windows proj4:x64-windows boost-system:x64-windows boost-filesystem:x64-windows boost-property-tree:x64-windows lua:x64-windows libpq:x64-windows + run: vcpkg install bzip2:x64-windows expat:x64-windows zlib:x64-windows proj4:x64-windows boost-geometry:x64-windows boost-system:x64-windows boost-filesystem:x64-windows boost-property-tree:x64-windows lua:x64-windows libpq:x64-windows shell: bash - name: Install psycopg2 and beahve diff --git a/README.md b/README.md index cbbe30b99..d8f25fd35 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,8 @@ Required libraries are * [proj](https://proj.org/) * [bzip2](http://www.bzip.org/) * [zlib](https://www.zlib.net/) -* [Boost libraries](https://www.boost.org/), including system and filesystem +* [Boost libraries](https://www.boost.org/), including geometry, system and + filesystem * [PostgreSQL](https://www.postgresql.org/) client libraries * [Lua](https://www.lua.org/) (Optional, used for Lua tag transforms and the flex output) diff --git a/src/geom-boost-adaptor.hpp b/src/geom-boost-adaptor.hpp new file mode 100644 index 000000000..c6c04ad80 --- /dev/null +++ b/src/geom-boost-adaptor.hpp @@ -0,0 +1,86 @@ +#ifndef OSM2PGSQL_GEOM_BOOST_ADAPTOR_HPP +#define OSM2PGSQL_GEOM_BOOST_ADAPTOR_HPP + +/** + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This file is part of osm2pgsql (https://osm2pgsql.org/). + * + * Copyright (C) 2006-2022 by the osm2pgsql developer community. + * For a full list of authors see the git log. + */ + +#include "geom.hpp" + +#include +#include +#include +#include +#include +#include +#include + +BOOST_GEOMETRY_REGISTER_POINT_2D_GET_SET(geom::point_t, double, cs::cartesian, + x, y, set_x, set_y) +BOOST_GEOMETRY_REGISTER_LINESTRING(geom::linestring_t) +BOOST_GEOMETRY_REGISTER_RING(geom::ring_t) + +BOOST_GEOMETRY_REGISTER_MULTI_POINT(geom::multipoint_t) +BOOST_GEOMETRY_REGISTER_MULTI_LINESTRING(geom::multilinestring_t) +BOOST_GEOMETRY_REGISTER_MULTI_POLYGON(geom::multipolygon_t) + +namespace boost { +namespace geometry { +namespace traits { +template <> +struct point_order<::geom::ring_t> +{ + static const order_selector value = counterclockwise; +}; + +template <> +struct tag<::geom::polygon_t> +{ + using type = polygon_tag; +}; +template <> +struct ring_const_type<::geom::polygon_t> +{ + using type = ::geom::ring_t const &; +}; +template <> +struct ring_mutable_type<::geom::polygon_t> +{ + using type = ::geom::ring_t &; +}; +template <> +struct interior_const_type<::geom::polygon_t> +{ + using type = std::vector<::geom::ring_t> const; +}; +template <> +struct interior_mutable_type<::geom::polygon_t> +{ + using type = std::vector<::geom::ring_t>; +}; + +template <> +struct exterior_ring<::geom::polygon_t> +{ + // NOLINTNEXTLINE(google-runtime-references) + static auto &get(::geom::polygon_t &p) { return p.outer(); } + static auto &get(::geom::polygon_t const &p) { return p.outer(); } +}; + +template <> +struct interior_rings<::geom::polygon_t> +{ + // NOLINTNEXTLINE(google-runtime-references) + static auto get(::geom::polygon_t &p) { return p.inners(); } + static auto get(::geom::polygon_t const &p) { return p.inners(); } +}; +} // namespace traits +} // namespace geometry +} // namespace boost + +#endif // OSM2PGSQL_GEOM_BOOST_ADAPTOR_HPP diff --git a/src/geom-functions.cpp b/src/geom-functions.cpp index 3785f34d4..50855a494 100644 --- a/src/geom-functions.cpp +++ b/src/geom-functions.cpp @@ -7,6 +7,7 @@ * For a full list of authors see the git log. */ +#include "geom-boost-adaptor.hpp" #include "geom-functions.hpp" #include @@ -30,6 +31,32 @@ point_t interpolate(point_t p1, point_t p2, double frac) noexcept frac * (p1.y() - p2.y()) + p2.y()}; } +std::string_view geometry_type(geometry_t const &geom) +{ + using namespace std::literals::string_view_literals; + return geom.visit(overloaded{ + [&](geom::nullgeom_t const & /*input*/) { return "NULL"sv; }, + [&](geom::point_t const & /*input*/) { return "POINT"sv; }, + [&](geom::linestring_t const & /*input*/) { return "LINESTRING"sv; }, + [&](geom::polygon_t const & /*input*/) { return "POLYGON"sv; }, + [&](geom::multipoint_t const & /*input*/) { return "MULTIPOINT"sv; }, + [&](geom::multilinestring_t const & /*input*/) { + return "MULTILINESTRING"sv; + }, + [&](geom::multipolygon_t const & /*input*/) { + return "MULTIPOLYGON"sv; + }, + [&](geom::collection_t const & /*input*/) { + return "GEOMETRYCOLLECTION"sv; + }}); +} + +std::size_t num_geometries(geometry_t const &geom) +{ + return geom.visit( + overloaded{[&](auto const &input) { return input.num_geometries(); }}); +} + namespace { class transform_visitor @@ -274,7 +301,7 @@ double area(geometry_t const &geom) } } - return total; + return std::abs(total); } namespace { @@ -519,4 +546,19 @@ geometry_t line_merge(geometry_t geom) return output; } +geometry_t centroid(geometry_t const &geom) +{ + geom::geometry_t output{point_t{}, geom.srid()}; + auto ¢er = output.get(); + + geom.visit(overloaded{ + [&](geom::nullgeom_t const & /*input*/) { output.reset(); }, + [&](geom::collection_t const & /*input*/) { + throw std::runtime_error{"not implemented yet"}; + }, + [&](auto const &input) { boost::geometry::centroid(input, center); }}); + + return output; +} + } // namespace geom diff --git a/src/geom-functions.hpp b/src/geom-functions.hpp index 18f1f221d..c5a419ae0 100644 --- a/src/geom-functions.hpp +++ b/src/geom-functions.hpp @@ -13,6 +13,7 @@ #include "geom.hpp" #include "reprojection.hpp" +#include #include #include @@ -51,6 +52,18 @@ void for_each_segment(point_list_t const &list, FUNC&& func) } } +/** + * Return the type of a geometry as string: NULL, (MULTI)POINT, + * (MULTI)LINESTRING, (MULTI)POLYGON, or GEOMETRYCOLLECTION. + */ +std::string_view geometry_type(geometry_t const &geom); + +/** + * Return the number of geometries in this geometry. For the null geometry + * this is always 0, for other non-multi geometries this is always 1. + */ +std::size_t num_geometries(geometry_t const &geom); + /** * Transform a geometry in 4326 into some other projection. * @@ -105,6 +118,14 @@ std::vector split_multi(geometry_t geom, bool split_multi = true); */ geometry_t line_merge(geometry_t geom); +/** + * Calculate the centroid of a geometry. + * + * \param geom Input geometry. + * \returns Resulting point geometry. + */ +geometry_t centroid(geometry_t const &geom); + } // namespace geom #endif // OSM2PGSQL_GEOM_FUNCTIONS_HPP diff --git a/src/geom.cpp b/src/geom.cpp index 54c6ba645..1d87d883e 100644 --- a/src/geom.cpp +++ b/src/geom.cpp @@ -23,4 +23,16 @@ bool operator!=(point_list_t const &a, point_list_t const &b) noexcept return !(a == b); } +bool operator==(polygon_t const &a, polygon_t const &b) noexcept +{ + return (a.outer() == b.outer()) && + std::equal(a.inners().cbegin(), a.inners().cend(), + b.inners().cbegin(), b.inners().cend()); +} + +bool operator!=(polygon_t const &a, polygon_t const &b) noexcept +{ + return !(a == b); +} + } // namespace geom diff --git a/src/geom.hpp b/src/geom.hpp index 18b1dc808..483ef7b0f 100644 --- a/src/geom.hpp +++ b/src/geom.hpp @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -29,6 +30,19 @@ namespace geom { class nullgeom_t { +public: + static std::size_t num_geometries() noexcept { return 0; } + + constexpr friend bool operator==(nullgeom_t, nullgeom_t) noexcept + { + return true; + } + + constexpr friend bool operator!=(nullgeom_t, nullgeom_t) noexcept + { + return false; + } + }; // class nullgeom_t class point_t @@ -42,6 +56,8 @@ class point_t constexpr point_t(double x, double y) noexcept : m_x(x), m_y(y) {} + static std::size_t num_geometries() noexcept { return 1; } + constexpr double x() const noexcept { return m_x; } constexpr double y() const noexcept { return m_y; } @@ -65,38 +81,41 @@ class point_t }; // class point_t /// This type is used as the basis for linestrings and rings. -using point_list_t = std::vector; - -bool operator==(point_list_t const &a, point_list_t const &b) noexcept; -bool operator!=(point_list_t const &a, point_list_t const &b) noexcept; - -class linestring_t : public point_list_t +class point_list_t : public std::vector { public: - linestring_t() = default; + point_list_t() = default; template - linestring_t(Iterator begin, Iterator end) : point_list_t(begin, end) + point_list_t(Iterator begin, Iterator end) + : std::vector(begin, end) {} - linestring_t(std::initializer_list list) - : point_list_t(list.begin(), list.end()) + point_list_t(std::initializer_list list) + : std::vector(list.begin(), list.end()) {} -}; // class linestring_t + static std::size_t num_geometries() noexcept { return 1; } -class ring_t : public point_list_t + friend bool operator==(point_list_t const &a, + point_list_t const &b) noexcept; + + friend bool operator!=(point_list_t const &a, + point_list_t const &b) noexcept; + +}; // class point_list_t + +class linestring_t : public point_list_t { public: - ring_t() = default; + using point_list_t::point_list_t; - template - ring_t(Iterator begin, Iterator end) : point_list_t(begin, end) - {} +}; // class linestring_t - ring_t(std::initializer_list list) - : point_list_t(list.begin(), list.end()) - {} +class ring_t : public point_list_t +{ +public: + using point_list_t::point_list_t; }; // class ring_t @@ -107,6 +126,8 @@ class polygon_t explicit polygon_t(ring_t &&ring) : m_outer(std::move(ring)) {} + static std::size_t num_geometries() noexcept { return 1; } + ring_t const &outer() const noexcept { return m_outer; } ring_t &outer() noexcept { return m_outer; } @@ -117,6 +138,10 @@ class polygon_t void add_inner_ring(ring_t &&ring) { m_inners.push_back(std::move(ring)); } + friend bool operator==(polygon_t const &a, polygon_t const &b) noexcept; + + friend bool operator!=(polygon_t const &a, polygon_t const &b) noexcept; + private: ring_t m_outer; std::vector m_inners; @@ -135,6 +160,18 @@ class multigeometry_t : public std::vector GEOM &add_geometry() { return this->emplace_back(); } + friend bool operator==(multigeometry_t const &a, + multigeometry_t const &b) noexcept + { + return std::equal(a.cbegin(), a.cend(), b.cbegin(), b.cend()); + } + + friend bool operator!=(multigeometry_t const &a, + multigeometry_t const &b) noexcept + { + return !(a == b); + } + }; // class multigeometry_t using multipoint_t = multigeometry_t; @@ -228,6 +265,16 @@ class geometry_t return std::visit(std::forward(visitor), m_geom); } + friend bool operator==(geometry_t const &a, geometry_t const &b) noexcept + { + return (a.srid() == b.srid()) && (a.m_geom == b.m_geom); + } + + friend bool operator!=(geometry_t const &a, geometry_t const &b) noexcept + { + return !(a == b); + } + private: std::variant @@ -239,4 +286,16 @@ class geometry_t } // namespace geom +// This magic is used for visiting geometries. For an explanation see for +// instance here: +// https://arne-mertz.de/2018/05/overload-build-a-variant-visitor-on-the-fly/ +template +struct overloaded : Ts... +{ + using Ts::operator()...; +}; + +template +overloaded(Ts...) -> overloaded; + #endif // OSM2PGSQL_GEOM_HPP diff --git a/src/wkb.cpp b/src/wkb.cpp index 7269d1b99..acdbaca3a 100644 --- a/src/wkb.cpp +++ b/src/wkb.cpp @@ -81,6 +81,16 @@ void write_length(std::string *data, std::size_t length) str_push(data, static_cast(length)); } +void write_point(std::string *data, geom::point_t const &geom, + uint32_t srid = 0) +{ + assert(data); + + write_header(data, wkb_point, srid); + str_push(data, geom.x()); + str_push(data, geom.y()); +} + void write_points(std::string *data, geom::point_list_t const &points) { write_length(data, points.size()); @@ -91,7 +101,7 @@ void write_points(std::string *data, geom::point_list_t const &points) } void write_linestring(std::string *data, geom::linestring_t const &geom, - uint32_t srid) + uint32_t srid = 0) { assert(data); @@ -100,7 +110,7 @@ void write_linestring(std::string *data, geom::linestring_t const &geom, } void write_polygon(std::string *data, geom::polygon_t const &geom, - uint32_t srid) + uint32_t srid = 0) { assert(data); @@ -112,6 +122,77 @@ void write_polygon(std::string *data, geom::polygon_t const &geom, } } +void write_multipoint(std::string *data, geom::multipoint_t const &geom, + uint32_t srid = 0) +{ + assert(data); + + write_header(data, wkb_multi_point, srid); + write_length(data, geom.num_geometries()); + + for (auto const &point : geom) { + write_point(data, point); + } +} + +void write_multilinestring(std::string *data, + geom::multilinestring_t const &geom, + uint32_t srid = 0) +{ + assert(data); + + write_header(data, wkb_multi_line, srid); + write_length(data, geom.num_geometries()); + + for (auto const &line : geom) { + write_linestring(data, line); + } +} + +void write_multipolygon(std::string *data, geom::multipolygon_t const &geom, + uint32_t srid = 0) +{ + assert(data); + + write_header(data, wkb_multi_polygon, srid); + write_length(data, geom.num_geometries()); + + for (auto const &polygon : geom) { + write_polygon(data, polygon); + } +} + +void write_collection(std::string *data, geom::collection_t const &geom, + uint32_t srid = 0) +{ + assert(data); + + write_header(data, wkb_collection, srid); + write_length(data, geom.num_geometries()); + + for (auto const &item : geom) { + item.visit(overloaded{ + [&](geom::nullgeom_t const & /*input*/) {}, + [&](geom::point_t const &input) { write_point(data, input); }, + [&](geom::linestring_t const &input) { + write_linestring(data, input); + }, + [&](geom::polygon_t const &input) { write_polygon(data, input); }, + [&](geom::multipoint_t const &input) { + write_multipoint(data, input); + }, + [&](geom::multilinestring_t const &input) { + write_multilinestring(data, input); + }, + [&](geom::multipolygon_t const &input) { + write_multipolygon(data, input); + }, + [&](geom::collection_t const &input) { + write_collection(data, input); + }}); + } +} + class make_ewkb_visitor { public: @@ -126,17 +207,19 @@ class make_ewkb_visitor std::string operator()(geom::point_t const &geom) const { - // 9 byte header plus one set of coordinates - constexpr const std::size_t size = 9 + 2 * 8; - std::string data; - data.reserve(size); - write_header(&data, wkb_point, m_srid); - str_push(&data, geom.x()); - str_push(&data, geom.y()); - - assert(data.size() == size); + if (m_ensure_multi) { + write_header(&data, wkb_multi_point, m_srid); + write_length(&data, 1); + write_point(&data, geom); + } else { + // 9 byte header plus one set of coordinates + constexpr const std::size_t size = 9 + 2 * 8; + data.reserve(size); + write_point(&data, geom, m_srid); + assert(data.size() == size); + } return data; } @@ -150,7 +233,7 @@ class make_ewkb_visitor data.reserve(2 * 13 + geom.size() * (2 * 8)); write_header(&data, wkb_multi_line, m_srid); write_length(&data, 1); - write_linestring(&data, geom, 0); + write_linestring(&data, geom); } else { // 13 byte header plus n sets of coordinates data.reserve(13 + geom.size() * (2 * 8)); @@ -167,7 +250,7 @@ class make_ewkb_visitor if (m_ensure_multi) { write_header(&data, wkb_multi_polygon, m_srid); write_length(&data, 1); - write_polygon(&data, geom, 0); + write_polygon(&data, geom); } else { write_polygon(&data, geom, m_srid); } @@ -175,44 +258,32 @@ class make_ewkb_visitor return data; } - std::string operator()(geom::multipoint_t const & /*geom*/) const + std::string operator()(geom::multipoint_t const &geom) const { - assert(false); - return {}; // XXX not used yet, no implementation + std::string data; + write_multipoint(&data, geom, m_srid); + return data; } std::string operator()(geom::multilinestring_t const &geom) const { std::string data; - - write_header(&data, wkb_multi_line, m_srid); - write_length(&data, geom.num_geometries()); - - for (auto const &line : geom) { - write_linestring(&data, line, 0); - } - + write_multilinestring(&data, geom, m_srid); return data; } std::string operator()(geom::multipolygon_t const &geom) const { std::string data; - - write_header(&data, wkb_multi_polygon, m_srid); - write_length(&data, geom.num_geometries()); - - for (auto const &polygon : geom) { - write_polygon(&data, polygon, 0); - } - + write_multipolygon(&data, geom, m_srid); return data; } - std::string operator()(geom::collection_t const & /*geom*/) const + std::string operator()(geom::collection_t const &geom) const { - assert(false); - return {}; // XXX not used yet, no implementation + std::string data; + write_collection(&data, geom, m_srid); + return data; } private: @@ -221,12 +292,24 @@ class make_ewkb_visitor }; // class make_ewkb_visitor +/** + * Parser for (E)WKB. + * + * Empty Multi* geometries and Geometry Collections will return a NULL + * geometry object. + * + * Call next() to get a pointer to the next character that is not part of the + * geometry any more. If this is not the same as the end pointer given to + * the constructor, this means that there is extra data available in the + * input data. + * + * Can only parse (E)WKB in native byte order. + */ class ewkb_parser_t { public: - explicit ewkb_parser_t(std::string const &wkb) - : m_it(wkb.data()), m_end(wkb.data() + wkb.size()), - m_max_length(wkb.size() / (sizeof(double) * 2)) + ewkb_parser_t(char const *it, char const *end) + : m_it(it), m_end(end), m_max_length((end - it) / (sizeof(double) * 2)) {} geom::geometry_t operator()() @@ -250,7 +333,7 @@ class ewkb_parser_t parse_polygon(&geom.set()); break; case geometry_type::wkb_multi_point: - // XXX not implemented yet + parse_multi_point(&geom); break; case geometry_type::wkb_multi_line: parse_multi_linestring(&geom); @@ -258,18 +341,19 @@ class ewkb_parser_t case geometry_type::wkb_multi_polygon: parse_multi_polygon(&geom); break; + case geometry_type::wkb_collection: + parse_collection(&geom); + break; default: throw std::runtime_error{ "Invalid WKB geometry: Unknown geometry type {}"_format(type)}; } - if (m_it != m_end) { - throw std::runtime_error{"Invalid WKB geometry: Extra data at end"}; - } - return geom; } + char const *next() const noexcept { return m_it; } + private: void check_bytes(uint32_t bytes) const { @@ -381,13 +465,35 @@ class ewkb_parser_t } } + void parse_multi_point(geom::geometry_t *geom) + { + auto &multipoint = geom->set(); + auto const num_geoms = parse_length(); + if (num_geoms == 0) { + geom->reset(); + return; + } + + multipoint.reserve(num_geoms); + for (uint32_t i = 0; i < num_geoms; ++i) { + auto &point = multipoint.emplace_back(); + uint32_t const type = parse_header(); + if (type != geometry_type::wkb_point) { + throw std::runtime_error{ + "Invalid WKB geometry: Multipoint containing" + " something other than point: {}"_format(type)}; + } + parse_point(&point); + } + } + void parse_multi_linestring(geom::geometry_t *geom) { auto &multilinestring = geom->set(); auto const num_geoms = parse_length(); if (num_geoms == 0) { - throw std::runtime_error{ - "Invalid WKB geometry: Multilinestring without linestrings"}; + geom->reset(); + return; } multilinestring.reserve(num_geoms); @@ -408,8 +514,8 @@ class ewkb_parser_t auto &multipolygon = geom->set(); auto const num_geoms = parse_length(); if (num_geoms == 0) { - throw std::runtime_error{ - "Invalid WKB geometry: Multipolygon without polygons"}; + geom->reset(); + return; } multipolygon.reserve(num_geoms); @@ -425,6 +531,23 @@ class ewkb_parser_t } } + void parse_collection(geom::geometry_t *geom) + { + auto &collection = geom->set(); + auto const num_geoms = parse_length(); + if (num_geoms == 0) { + geom->reset(); + return; + } + + collection.reserve(num_geoms); + for (uint32_t i = 0; i < num_geoms; ++i) { + ewkb_parser_t parser{m_it, m_end}; + collection.add_geometry(parser()); + m_it = parser.next(); + } + } + char const *m_it; char const *m_end; uint32_t m_max_length; @@ -443,8 +566,15 @@ std::string geom_to_ewkb(geom::geometry_t const &geom, bool ensure_multi) geom::geometry_t ewkb_to_geom(std::string const &wkb) { - ewkb::ewkb_parser_t parser{wkb}; - return parser(); + const char *const end = wkb.data() + wkb.size(); + ewkb::ewkb_parser_t parser{wkb.data(), end}; + auto geom = parser(); + + if (parser.next() != end) { + throw std::runtime_error{"Invalid WKB geometry: Extra data at end"}; + } + + return geom; } unsigned char decode_hex_char(char c) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2b3583c32..4e9b42b8d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,8 +42,11 @@ set_test(test-db-copy-thread) set_test(test-db-copy-mgr) set_test(test-domain-matcher LABELS NoDB) set_test(test-expire-tiles LABELS NoDB) -set_test(test-geom LABELS NoDB) set_test(test-geom-box LABELS NoDB) +set_test(test-geom-lines LABELS NoDB) +set_test(test-geom-null LABELS NoDB) +set_test(test-geom-points LABELS NoDB) +set_test(test-geom-polygons LABELS NoDB) set_test(test-middle) set_test(test-node-locations LABELS NoDB) set_test(test-options-database LABELS NoDB) diff --git a/tests/test-geom.cpp b/tests/test-geom-lines.cpp similarity index 94% rename from tests/test-geom.cpp rename to tests/test-geom-lines.cpp index 5759f28e0..41ae911a2 100644 --- a/tests/test-geom.cpp +++ b/tests/test-geom-lines.cpp @@ -14,39 +14,9 @@ #include "geom-from-osm.hpp" #include "geom-functions.hpp" #include "geom.hpp" -#include "reprojection.hpp" #include -TEST_CASE("geom::distance", "[NoDB]") -{ - geom::point_t const p1{10, 10}; - geom::point_t const p2{20, 10}; - geom::point_t const p3{13, 14}; - - REQUIRE(geom::distance(p1, p1) == Approx(0.0)); - REQUIRE(geom::distance(p1, p2) == Approx(10.0)); - REQUIRE(geom::distance(p1, p3) == Approx(5.0)); -} - -TEST_CASE("geom::interpolate", "[NoDB]") -{ - geom::point_t const p1{10, 10}; - geom::point_t const p2{20, 10}; - - auto const i1 = geom::interpolate(p1, p1, 0.5); - REQUIRE(i1.x() == 10); - REQUIRE(i1.y() == 10); - - auto const i2 = geom::interpolate(p1, p2, 0.5); - REQUIRE(i2.x() == 15); - REQUIRE(i2.y() == 10); - - auto const i3 = geom::interpolate(p2, p1, 0.5); - REQUIRE(i3.x() == 15); - REQUIRE(i3.y() == 10); -} - TEST_CASE("geom::linestring_t", "[NoDB]") { geom::linestring_t ls1; @@ -64,6 +34,18 @@ TEST_CASE("geom::linestring_t", "[NoDB]") REQUIRE(it->y() == 22); ++it; REQUIRE(it == ls1.cend()); + + REQUIRE(ls1.num_geometries() == 1); +} + +TEST_CASE("line geometry", "[NoDB]") +{ + geom::geometry_t const geom{geom::linestring_t{{1, 1}, {2, 2}}}; + + REQUIRE(num_geometries(geom) == 1); + REQUIRE(area(geom) == Approx(0.0)); + REQUIRE(geometry_type(geom) == "LINESTRING"); + REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1.5, 1.5}}); } TEST_CASE("geom::segmentize w/o split", "[NoDB]") @@ -73,6 +55,7 @@ TEST_CASE("geom::segmentize w/o split", "[NoDB]") auto const geom = geom::segmentize(geom::geometry_t{line}, 10.0); REQUIRE(geom.is_multilinestring()); + REQUIRE(num_geometries(geom) == 1); auto const &ml = geom.get(); REQUIRE(ml.num_geometries() == 1); REQUIRE(ml[0] == line); @@ -182,6 +165,9 @@ TEST_CASE("create_multilinestring with single line", "[NoDB]") geom::line_merge(geom::create_multilinestring(buffer.buffer())); REQUIRE(geom.is_multilinestring()); + REQUIRE(geometry_type(geom) == "MULTILINESTRING"); + REQUIRE(num_geometries(geom) == 1); + REQUIRE(area(geom) == Approx(0.0)); auto const &ml = geom.get(); REQUIRE(ml.num_geometries() == 1); REQUIRE(ml[0] == expected); diff --git a/tests/test-geom-null.cpp b/tests/test-geom-null.cpp new file mode 100644 index 000000000..7fc32e89a --- /dev/null +++ b/tests/test-geom-null.cpp @@ -0,0 +1,24 @@ +/** + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This file is part of osm2pgsql (https://osm2pgsql.org/). + * + * Copyright (C) 2006-2022 by the osm2pgsql developer community. + * For a full list of authors see the git log. + */ + +#include + +#include "geom-from-osm.hpp" +#include "geom-functions.hpp" +#include "geom.hpp" + +TEST_CASE("null geometry", "[NoDB]") +{ + geom::geometry_t const geom{}; + + REQUIRE(num_geometries(geom) == 0); + REQUIRE(area(geom) == 0.0); + REQUIRE(geometry_type(geom) == "NULL"); + REQUIRE(centroid(geom).is_null()); +} diff --git a/tests/test-geom-points.cpp b/tests/test-geom-points.cpp new file mode 100644 index 000000000..10ed93d06 --- /dev/null +++ b/tests/test-geom-points.cpp @@ -0,0 +1,86 @@ +/** + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This file is part of osm2pgsql (https://osm2pgsql.org/). + * + * Copyright (C) 2006-2022 by the osm2pgsql developer community. + * For a full list of authors see the git log. + */ + +#include + +#include "common-buffer.hpp" + +#include "geom-from-osm.hpp" +#include "geom-functions.hpp" +#include "geom.hpp" + +TEST_CASE("geom::point_t", "[NoDB]") +{ + geom::point_t p; + + REQUIRE(p.x() == Approx(0.0)); + REQUIRE(p.y() == Approx(0.0)); + + p.set_x(1.2); + p.set_y(3.4); + + REQUIRE(p.x() == Approx(1.2)); + REQUIRE(p.y() == Approx(3.4)); + + REQUIRE(p.num_geometries() == 1); +} + +TEST_CASE("geom::point_t from location", "[NoDB]") +{ + osmium::Location const location{3.141, 2.718}; + geom::point_t const p{location}; + + REQUIRE(p.x() == Approx(3.141)); + REQUIRE(p.y() == Approx(2.718)); + REQUIRE(p == geom::point_t{3.141, 2.718}); +} + +TEST_CASE("create_point from OSM data", "[NoDB]") +{ + test_buffer_t buffer; + buffer.add_node("n10 x1.1 y2.2"); + + auto const geom = geom::create_point(buffer.buffer().get(0)); + + REQUIRE(geom.is_point()); + REQUIRE(geometry_type(geom) == "POINT"); + REQUIRE(num_geometries(geom) == 1); + REQUIRE(area(geom) == Approx(0.0)); + REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1.1, 2.2}}); + REQUIRE(geom.get() == geom::point_t{1.1, 2.2}); +} + +TEST_CASE("geom::distance", "[NoDB]") +{ + geom::point_t const p1{10, 10}; + geom::point_t const p2{20, 10}; + geom::point_t const p3{13, 14}; + + REQUIRE(geom::distance(p1, p1) == Approx(0.0)); + REQUIRE(geom::distance(p1, p2) == Approx(10.0)); + REQUIRE(geom::distance(p1, p3) == Approx(5.0)); +} + +TEST_CASE("geom::interpolate", "[NoDB]") +{ + geom::point_t const p1{10, 10}; + geom::point_t const p2{20, 10}; + + auto const i1 = geom::interpolate(p1, p1, 0.5); + REQUIRE(i1.x() == 10); + REQUIRE(i1.y() == 10); + + auto const i2 = geom::interpolate(p1, p2, 0.5); + REQUIRE(i2.x() == 15); + REQUIRE(i2.y() == 10); + + auto const i3 = geom::interpolate(p2, p1, 0.5); + REQUIRE(i3.x() == 15); + REQUIRE(i3.y() == 10); +} diff --git a/tests/test-geom-polygons.cpp b/tests/test-geom-polygons.cpp new file mode 100644 index 000000000..7d9a11fce --- /dev/null +++ b/tests/test-geom-polygons.cpp @@ -0,0 +1,65 @@ +/** + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This file is part of osm2pgsql (https://osm2pgsql.org/). + * + * Copyright (C) 2006-2022 by the osm2pgsql developer community. + * For a full list of authors see the git log. + */ + +#include + +#include "geom-functions.hpp" +#include "geom.hpp" + +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(num_geometries(geom) == 1); + REQUIRE(area(geom) == Approx(1.0)); + REQUIRE(geometry_type(geom) == "POLYGON"); + REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{0.5, 0.5}}); +} + +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(num_geometries(geom) == 1); + REQUIRE(area(geom) == Approx(1.0)); + REQUIRE(geometry_type(geom) == "POLYGON"); + REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{0.5, 0.5}}); +} + +TEST_CASE("polygon geometry with inner", "[NoDB]") +{ + geom::geometry_t const geom{ + geom::polygon_t{geom::ring_t{{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}}}; + + REQUIRE(num_geometries(geom) == 1); + REQUIRE(area(geom) == Approx(1.0)); + REQUIRE(geometry_type(geom) == "POLYGON"); + REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{0.5, 0.5}}); +} + +TEST_CASE("geom::polygon_t", "[NoDB]") +{ + geom::polygon_t polygon; + + REQUIRE(polygon.outer().empty()); + polygon.outer() = geom::ring_t{{0, 0}, {0, 3}, {3, 3}, {3, 0}, {0, 0}}; + polygon.inners().emplace_back( + geom::ring_t{{1, 1}, {2, 1}, {2, 2}, {1, 2}, {1, 1}}); + + REQUIRE(polygon.num_geometries() == 1); + REQUIRE(polygon.inners().size() == 1); + + geom::geometry_t geom{std::move(polygon)}; + REQUIRE(num_geometries(geom) == 1); + REQUIRE(area(geom) == Approx(8.0)); + REQUIRE(geometry_type(geom) == "POLYGON"); + REQUIRE(centroid(geom) == geom::geometry_t{geom::point_t{1.5, 1.5}}); +} diff --git a/tests/test-wkb.cpp b/tests/test-wkb.cpp index 54cf8a7bf..a7cd63fcc 100644 --- a/tests/test-wkb.cpp +++ b/tests/test-wkb.cpp @@ -14,7 +14,8 @@ TEST_CASE("wkb: nullgeom", "[NoDB]") { - geom::geometry_t geom{}; + geom::geometry_t const geom{}; + REQUIRE(geom.is_null()); auto const wkb = geom_to_ewkb(geom); REQUIRE(wkb.empty()); @@ -25,25 +26,23 @@ TEST_CASE("wkb: nullgeom", "[NoDB]") TEST_CASE("wkb: point", "[NoDB]") { - geom::geometry_t geom{geom::point_t{3.14, 2.17}, 42}; + geom::geometry_t const geom{geom::point_t{3.14, 2.17}, 42}; auto const result = ewkb_to_geom(geom_to_ewkb(geom)); REQUIRE(result.is_point()); REQUIRE(result.srid() == 42); - - REQUIRE(result.get() == geom.get()); + REQUIRE(result == geom); } TEST_CASE("wkb: linestring", "[NoDB]") { - geom::geometry_t geom{ + geom::geometry_t const geom{ geom::linestring_t{{1.2, 2.3}, {3.4, 4.5}, {5.6, 6.7}}, 43}; auto const result = ewkb_to_geom(geom_to_ewkb(geom)); REQUIRE(result.is_linestring()); REQUIRE(result.srid() == 43); - - REQUIRE(result.get() == geom.get()); + REQUIRE(result == geom); } TEST_CASE("wkb: polygon without inner ring", "[NoDB]") @@ -56,10 +55,7 @@ TEST_CASE("wkb: polygon without inner ring", "[NoDB]") auto const result = ewkb_to_geom(geom_to_ewkb(geom)); REQUIRE(result.is_polygon()); REQUIRE(result.srid() == 44); - REQUIRE(result.get().inners().empty()); - - REQUIRE(result.get().outer() == - geom.get().outer()); + REQUIRE(result == geom); } TEST_CASE("wkb: polygon with inner rings", "[NoDB]") @@ -75,25 +71,47 @@ TEST_CASE("wkb: polygon with inner rings", "[NoDB]") auto const result = ewkb_to_geom(geom_to_ewkb(geom)); REQUIRE(result.is_polygon()); REQUIRE(result.srid() == 45); - REQUIRE(result.get().inners().size() == 1); + REQUIRE(result == geom); +} + +TEST_CASE("wkb: point as multipoint", "[NoDB]") +{ + geom::geometry_t const geom{geom::point_t{1.2, 2.3}, 47}; + + auto const result = ewkb_to_geom(geom_to_ewkb(geom, true)); + REQUIRE(result.is_multipoint()); + REQUIRE(result.srid() == 47); + + auto const &rmp = result.get(); + REQUIRE(rmp.num_geometries() == 1); + REQUIRE(rmp[0] == geom.get()); +} - REQUIRE(result.get().outer() == - geom.get().outer()); - REQUIRE(result.get().inners().front() == - geom.get().inners().front()); +TEST_CASE("wkb: multipoint", "[NoDB]") +{ + geom::geometry_t geom{geom::multipoint_t{}, 46}; + + auto &mp = geom.get(); + mp.emplace_back(geom::point_t{{1.2, 2.3}}); + mp.emplace_back(geom::point_t{{7.0, 7.0}}); + + auto const result = ewkb_to_geom(geom_to_ewkb(geom)); + REQUIRE(result.is_multipoint()); + REQUIRE(result.srid() == 46); + REQUIRE(result == geom); } TEST_CASE("wkb: linestring as multilinestring", "[NoDB]") { - geom::geometry_t geom{ + geom::geometry_t const geom{ geom::linestring_t{{1.2, 2.3}, {3.4, 4.5}, {5.6, 6.7}}, 43}; auto const result = ewkb_to_geom(geom_to_ewkb(geom, true)); REQUIRE(result.is_multilinestring()); REQUIRE(result.srid() == 43); + auto const &rml = result.get(); REQUIRE(rml.num_geometries() == 1); - REQUIRE(rml[0] == geom.get()); } @@ -108,11 +126,7 @@ TEST_CASE("wkb: multilinestring", "[NoDB]") auto const result = ewkb_to_geom(geom_to_ewkb(geom)); REQUIRE(result.is_multilinestring()); REQUIRE(result.srid() == 46); - auto const &rml = result.get(); - REQUIRE(rml.num_geometries() == 2); - - REQUIRE(rml[0] == ml[0]); - REQUIRE(rml[1] == ml[1]); + REQUIRE(result == geom); } TEST_CASE("wkb: polygon as multipolygon", "[NoDB]") @@ -125,11 +139,10 @@ TEST_CASE("wkb: polygon as multipolygon", "[NoDB]") auto const result = ewkb_to_geom(geom_to_ewkb(geom, true)); REQUIRE(result.is_multipolygon()); REQUIRE(result.srid() == 44); + auto const &rmp = result.get(); REQUIRE(rmp.num_geometries() == 1); - - REQUIRE(rmp[0].outer() == geom.get().outer()); - REQUIRE(rmp[0].inners().empty()); + REQUIRE(rmp[0] == geom.get()); } TEST_CASE("wkb: multipolygon", "[NoDB]") @@ -150,22 +163,32 @@ TEST_CASE("wkb: multipolygon", "[NoDB]") auto const result = ewkb_to_geom(geom_to_ewkb(geom)); REQUIRE(result.is_multipolygon()); REQUIRE(result.srid() == 47); - auto const &rmp = result.get(); - REQUIRE(rmp.num_geometries() == 2); - - REQUIRE(rmp[0].outer() == mp[0].outer()); - REQUIRE(rmp[0].inners().size() == 1); - REQUIRE(rmp[0].inners().front() == mp[0].inners().front()); - - REQUIRE(rmp[1].outer() == mp[1].outer()); - REQUIRE(rmp[1].inners().empty()); + REQUIRE(result == geom); } -TEST_CASE("wkb: invalid", "[NoDB]") +TEST_CASE("wkb: geometrycollection", "[NoDB]") { - REQUIRE_THROWS(ewkb_to_geom("INVALID")); + geom::geometry_t geom1{geom::point_t{1.0, 2.0}}; + geom::geometry_t geom2{geom::linestring_t{{1.2, 2.3}, {3.4, 4.5}}}; + geom::geometry_t geom3{geom::multipolygon_t{}}; + + geom3.get().emplace_back(geom::ring_t{ + {4.0, 4.0}, {5.0, 4.0}, {5.0, 5.0}, {4.0, 5.0}, {4.0, 4.0}}); + + geom::geometry_t geom{geom::collection_t{}, 49}; + auto &c = geom.get(); + c.add_geometry(std::move(geom1)); + c.add_geometry(std::move(geom2)); + c.add_geometry(std::move(geom3)); + + auto const result = ewkb_to_geom(geom_to_ewkb(geom)); + REQUIRE(result.is_collection()); + REQUIRE(result.srid() == 49); + REQUIRE(result == geom); } +TEST_CASE("wkb: invalid", "[NoDB]") { REQUIRE_THROWS(ewkb_to_geom("INVALID")); } + TEST_CASE("wkb hex decode of valid hex characters") { REQUIRE(decode_hex_char('0') == 0);