From 4bfa132382ef592c75021fc1ff6bf232b2f9f706 Mon Sep 17 00:00:00 2001 From: Jochen Topf Date: Mon, 3 Oct 2022 14:46:03 +0200 Subject: [PATCH] Refactor expire by geometry code, disable expire when not using 3857 Split up code into functions for each geometry type. Adds support for geometry types that have been missing so far (multipoint, geometry collection). When using a target SRS other than Mercator (EPSG:3857) expire didn't work before, it is now explicitly disabled (with a warning) in this case. (This applies only to the pgsql output, the flex output sets the SRS per table. There is currently no warning in this case, but we are moving away from the expire configuration on the command line for the flex output anyway, so I'll leave it at that for the moment.) --- src/expire-tiles.cpp | 77 +++-- src/expire-tiles.hpp | 18 ++ src/options.cpp | 6 + src/output-flex.cpp | 17 +- src/output-pgsql.cpp | 26 +- tests/CMakeLists.txt | 1 + tests/test-expire-from-geometry.cpp | 433 ++++++++++++++++++++++++++++ 7 files changed, 527 insertions(+), 51 deletions(-) create mode 100644 tests/test-expire-from-geometry.cpp diff --git a/src/expire-tiles.cpp b/src/expire-tiles.cpp index 259182d2a..f19f495c2 100644 --- a/src/expire-tiles.cpp +++ b/src/expire-tiles.cpp @@ -78,41 +78,57 @@ void expire_tiles::from_point_list(geom::point_list_t const &list) }); } -void expire_tiles::from_geometry(geom::geometry_t const &geom) +void expire_tiles::from_geometry(geom::point_t const &geom) +{ + geom::box_t const box = geom::envelope(geom); + from_bbox(box); +} + +void expire_tiles::from_geometry(geom::linestring_t const &geom) { - if (geom.srid() != 3857) { - return; + from_point_list(geom); +} + +void expire_tiles::from_polygon_boundary(geom::polygon_t const &geom) +{ + from_point_list(geom.outer()); + for (auto const &inner : geom.inners()) { + from_point_list(inner); } +} - if (geom.is_point()) { - auto const box = geom::envelope(geom); - from_bbox(box); - } else if (geom.is_linestring()) { - from_point_list(geom.get()); - } else if (geom.is_multilinestring()) { - for (auto const &list : geom.get()) { - from_point_list(list); - } - } else if (geom.is_polygon() || geom.is_multipolygon()) { - auto const box = geom::envelope(geom); - if (from_bbox(box)) { - if (geom.is_polygon()) { - from_point_list(geom.get().outer()); - for (auto const &inner : geom.get().inners()) { - from_point_list(inner); - } - } else if (geom.is_multipolygon()) { - for (auto const &polygon : geom.get()) { - from_point_list(polygon.outer()); - for (auto const &inner : polygon.inners()) { - from_point_list(inner); - } - } - } +void expire_tiles::from_geometry(geom::polygon_t const &geom) +{ + geom::box_t const box = geom::envelope(geom); + if (from_bbox(box)) { + /* Bounding box too big - just expire tiles on the boundary */ + from_polygon_boundary(geom); + } +} + +void expire_tiles::from_geometry(geom::multipolygon_t const &geom) +{ + geom::box_t const box = geom::envelope(geom); + if (from_bbox(box)) { + /* Bounding box too big - just expire tiles on the boundary */ + for (auto const &sgeom : geom) { + from_polygon_boundary(sgeom); } } } +void expire_tiles::from_geometry(geom::geometry_t const &geom) +{ + geom.visit([&](auto const &g) { from_geometry(g); }); +} + +void expire_tiles::from_geometry_if_3857(geom::geometry_t const &geom) +{ + if (geom.srid() == 3857) { + from_geometry(geom); + } +} + /* * Expire tiles that a line crosses */ @@ -281,11 +297,8 @@ std::size_t output_tiles_to_file(quadkey_list_t const &tiles_maxzoom, int expire_from_result(expire_tiles *expire, pg_result_t const &result) { - if (!expire->enabled()) { - return -1; - } - auto const num_tuples = result.num_tuples(); + for (int i = 0; i < num_tuples; ++i) { char const *const wkb = result.get_value(i, 0); expire->from_geometry(ewkb_to_geom(decode_hex(wkb))); diff --git a/src/expire-tiles.hpp b/src/expire-tiles.hpp index 0ff417b09..a6aabbaec 100644 --- a/src/expire-tiles.hpp +++ b/src/expire-tiles.hpp @@ -32,8 +32,26 @@ class expire_tiles bool enabled() const noexcept { return m_maxzoom != 0; } + void from_polygon_boundary(geom::polygon_t const &geom); + + void from_geometry(geom::nullgeom_t const & /*geom*/) {} + void from_geometry(geom::point_t const &geom); + void from_geometry(geom::linestring_t const &geom); + void from_geometry(geom::polygon_t const &geom); + void from_geometry(geom::multipolygon_t const &geom); + + template + void from_geometry(geom::multigeometry_t const &geom) + { + for (auto const &sgeom : geom) { + from_geometry(sgeom); + } + } + void from_geometry(geom::geometry_t const &geom); + void from_geometry_if_3857(geom::geometry_t const &geom); + int from_bbox(geom::box_t const &box); /** diff --git a/src/options.cpp b/src/options.cpp index f40cdc58f..da21b4105 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -710,6 +710,12 @@ void options_t::check_options() "large and has been set to 31."); } + if (expire_tiles_zoom != 0 && projection->target_srs() != 3857) { + log_warn("Expire has been enabled (with -e or --expire-tiles) but " + "target SRS is not Mercator (EPSG:3857). Expire disabled!"); + expire_tiles_zoom = 0; + } + if (output_backend == "flex" || output_backend == "gazetteer") { if (style == DEFAULT_STYLE) { throw std::runtime_error{ diff --git a/src/output-flex.cpp b/src/output-flex.cpp index 6b60f4f85..1d322df05 100644 --- a/src/output-flex.cpp +++ b/src/output-flex.cpp @@ -711,13 +711,13 @@ void output_flex_t::write_column( type == table_column_type::multilinestring || type == table_column_type::multipolygon); if (geom->srid() == column.srid()) { - m_expire.from_geometry(*geom); + m_expire.from_geometry_if_3857(*geom); copy_mgr->add_hex_geom(geom_to_ewkb(*geom, wrap_multi)); } else { auto const proj = reprojection::create_projection(column.srid()); auto const tgeom = geom::transform(*geom, *proj); - m_expire.from_geometry(tgeom); + m_expire.from_geometry_if_3857(tgeom); copy_mgr->add_hex_geom(geom_to_ewkb(tgeom, wrap_multi)); } } else { @@ -1681,7 +1681,7 @@ void output_flex_t::add_row(table_connection_t *table_connection, auto const geoms = geom::split_multi(std::move(geom), split_multi); for (auto const &sgeom : geoms) { - m_expire.from_geometry(sgeom); + m_expire.from_geometry_if_3857(sgeom); write_row(table_connection, object.type(), id, sgeom, table.geom_column().srid()); } @@ -1906,15 +1906,12 @@ void output_flex_t::delete_from_table(table_connection_t *table_connection, osmium::item_type type, osmid_t osm_id) { assert(table_connection); - auto const id = table_connection->table().map_id(type, osm_id); + auto const &table = table_connection->table(); + auto const id = table.map_id(type, osm_id); - if (m_expire.enabled() && table_connection->table().has_geom_column()) { + if (m_expire.enabled() && table.has_geom_column() && + table.geom_column().srid() == 3857) { auto const result = table_connection->get_geom_by_id(type, id); - - if (result.num_tuples() == 0) { - return; - } - expire_from_result(&m_expire, result); } diff --git a/src/output-pgsql.cpp b/src/output-pgsql.cpp index db6fc9ba9..b16443faf 100644 --- a/src/output-pgsql.cpp +++ b/src/output-pgsql.cpp @@ -67,7 +67,7 @@ void output_pgsql_t::pgsql_out_way(osmium::Way const &way, taglist_t *tags, auto const wkb = geom_to_ewkb(projected_geom); if (!wkb.empty()) { - m_expire.from_geometry(projected_geom); + m_expire.from_geometry_if_3857(projected_geom); if (m_enable_way_area) { double const area = calculate_area( get_options()->reproject_area, geom, projected_geom); @@ -82,7 +82,7 @@ void output_pgsql_t::pgsql_out_way(osmium::Way const &way, taglist_t *tags, auto const geoms = geom::split_multi(geom::segmentize( geom::transform(geom::create_linestring(way), *m_proj), split_at)); for (auto const &sgeom : geoms) { - m_expire.from_geometry(sgeom); + m_expire.from_geometry_if_3857(sgeom); auto const wkb = geom_to_ewkb(sgeom); m_tables[t_line]->write_row(way.id(), *tags, wkb); if (roads) { @@ -169,7 +169,7 @@ void output_pgsql_t::node_add(osmium::Node const &node) } auto const geom = geom::transform(geom::create_point(node), *m_proj); - m_expire.from_geometry(geom); + m_expire.from_geometry_if_3857(geom); auto const wkb = geom_to_ewkb(geom); m_tables[t_point]->write_row(node.id(), outtags, wkb); } @@ -272,7 +272,7 @@ void output_pgsql_t::pgsql_process_relation(osmium::Relation const &rel) } auto const geoms = geom::split_multi(std::move(projected_geom)); for (auto const &sgeom : geoms) { - m_expire.from_geometry(sgeom); + m_expire.from_geometry_if_3857(sgeom); auto const wkb = geom_to_ewkb(sgeom); m_tables[t_line]->write_row(-rel.id(), outtags, wkb); if (roads) { @@ -288,7 +288,7 @@ void output_pgsql_t::pgsql_process_relation(osmium::Relation const &rel) !get_options()->enable_multi); for (auto const &sgeom : geoms) { auto const projected_geom = geom::transform(sgeom, *m_proj); - m_expire.from_geometry(projected_geom); + m_expire.from_geometry_if_3857(projected_geom); auto const wkb = geom_to_ewkb(projected_geom); if (m_enable_way_area) { double const area = calculate_area( @@ -325,8 +325,12 @@ void output_pgsql_t::relation_add(osmium::Relation const &rel) * contain the change for that also. */ void output_pgsql_t::node_delete(osmid_t osm_id) { - auto const results = m_tables[t_point]->get_wkb(osm_id); - if (expire_from_result(&m_expire, results) != 0) { + if (m_expire.enabled()) { + auto const results = m_tables[t_point]->get_wkb(osm_id); + if (expire_from_result(&m_expire, results) != 0) { + m_tables[t_point]->delete_row(osm_id); + } + } else { m_tables[t_point]->delete_row(osm_id); } } @@ -336,8 +340,12 @@ void output_pgsql_t::delete_from_output_and_expire(osmid_t id) m_tables[t_roads]->delete_row(id); for (auto table : {t_line, t_poly}) { - auto const results = m_tables[table]->get_wkb(id); - if (expire_from_result(&m_expire, results) != 0) { + if (m_expire.enabled()) { + auto const results = m_tables[table]->get_wkb(id); + if (expire_from_result(&m_expire, results) != 0) { + m_tables[table]->delete_row(id); + } + } else { m_tables[table]->delete_row(id); } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 69c287924..5a5a6f0df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,6 +41,7 @@ set_test(test-check-input LABELS NoDB) set_test(test-db-copy-thread) set_test(test-db-copy-mgr) set_test(test-domain-matcher LABELS NoDB) +set_test(test-expire-from-geometry LABELS NoDB) set_test(test-expire-tiles LABELS NoDB) set_test(test-geom-box LABELS NoDB) set_test(test-geom-collections LABELS NoDB) diff --git a/tests/test-expire-from-geometry.cpp b/tests/test-expire-from-geometry.cpp new file mode 100644 index 000000000..5944e2973 --- /dev/null +++ b/tests/test-expire-from-geometry.cpp @@ -0,0 +1,433 @@ +/** + * 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 +#include + +#include "expire-tiles.hpp" +#include "reprojection.hpp" +#include "tile-output.hpp" +#include "tile.hpp" + +static std::shared_ptr defproj{ + reprojection::create_projection(PROJ_SPHERE_MERC)}; + +// We are using zoom level 12 here, because at that level a tile is about +// 10,000 units wide/high which gives us easy numbers to work with. +static constexpr uint32_t const zoom = 12; + +TEST_CASE("expire null geometry does nothing", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + SECTION("geom") + { + geom::geometry_t geom{}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + REQUIRE(et.get_tiles().empty()); +} + +TEST_CASE("expire point at tile boundary", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + geom::point_t pt{0.0, 0.0}; + + SECTION("point") { et.from_geometry(pt); } + + SECTION("geom") + { + geom::geometry_t geom{pt}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{pt}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 4); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2047, 2047}); + CHECK(tile_t::from_quadkey(tiles[1], zoom) == tile_t{zoom, 2048, 2047}); + CHECK(tile_t::from_quadkey(tiles[2], zoom) == tile_t{zoom, 2047, 2048}); + CHECK(tile_t::from_quadkey(tiles[3], zoom) == tile_t{zoom, 2048, 2048}); +} + +TEST_CASE("expire point away from tile boundary", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + geom::point_t pt{5000.0, 5000.0}; + + SECTION("point") { et.from_geometry(pt); } + + SECTION("geom") + { + geom::geometry_t geom{pt}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{pt}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 1); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2048, 2047}); +} + +TEST_CASE("expire linestring away from tile boundary", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + geom::linestring_t line{{5000.0, 4000.0}, {5100.0, 4200.0}}; + + SECTION("line") { et.from_geometry(line); } + + SECTION("geom") + { + geom::geometry_t geom{std::move(line)}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{std::move(line)}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 1); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2048, 2047}); +} + +TEST_CASE("expire linestring crossing tile boundary", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + geom::linestring_t line{{5000.0, 5000.0}, {5000.0, 15000.0}}; + + SECTION("line") { et.from_geometry(line); } + + SECTION("geom") + { + geom::geometry_t geom{std::move(line)}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{std::move(line)}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 2); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2048, 2046}); + CHECK(tile_t::from_quadkey(tiles[1], zoom) == tile_t{zoom, 2048, 2047}); +} + +TEST_CASE("expire small polygon", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + geom::polygon_t poly{{{5000.0, 5000.0}, + {5100.0, 5000.0}, + {5100.0, 5100.0}, + {5000.0, 5100.0}, + {5000.0, 5000.0}}}; + + SECTION("polygon") { et.from_geometry(poly); } + + SECTION("geom") + { + geom::geometry_t geom{std::move(poly)}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{std::move(poly)}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 1); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2048, 2047}); +} + +TEST_CASE("expire large polygon as bbox", "[NoDB]") +{ + expire_tiles et{zoom, 40000, defproj}; + + geom::polygon_t poly{{{5000.0, 5000.0}, + {25000.0, 5000.0}, + {25000.0, 25000.0}, + {5000.0, 25000.0}, + {5000.0, 5000.0}}}; + + SECTION("polygon") { et.from_geometry(poly); } + + SECTION("geom") + { + geom::geometry_t geom{std::move(poly)}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{std::move(poly)}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 9); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2048, 2045}); + CHECK(tile_t::from_quadkey(tiles[1], zoom) == tile_t{zoom, 2049, 2045}); + CHECK(tile_t::from_quadkey(tiles[2], zoom) == tile_t{zoom, 2050, 2045}); + + CHECK(tile_t::from_quadkey(tiles[3], zoom) == tile_t{zoom, 2048, 2046}); + CHECK(tile_t::from_quadkey(tiles[4], zoom) == tile_t{zoom, 2049, 2046}); + CHECK(tile_t::from_quadkey(tiles[7], zoom) == tile_t{zoom, 2050, 2046}); + + CHECK(tile_t::from_quadkey(tiles[5], zoom) == tile_t{zoom, 2048, 2047}); + CHECK(tile_t::from_quadkey(tiles[6], zoom) == tile_t{zoom, 2049, 2047}); + CHECK(tile_t::from_quadkey(tiles[8], zoom) == tile_t{zoom, 2050, 2047}); +} + +TEST_CASE("expire large polygon as boundary", "[NoDB]") +{ + expire_tiles et{zoom, 10000, defproj}; + + geom::polygon_t poly{{{5000.0, 5000.0}, + {25000.0, 5000.0}, + {25000.0, 25000.0}, + {5000.0, 25000.0}, + {5000.0, 5000.0}}}; + + SECTION("polygon") { et.from_geometry(poly); } + + SECTION("polygon boundary") { et.from_polygon_boundary(poly); } + + SECTION("geom") + { + geom::geometry_t geom{std::move(poly)}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{std::move(poly)}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 8); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2048, 2045}); + CHECK(tile_t::from_quadkey(tiles[1], zoom) == tile_t{zoom, 2049, 2045}); + CHECK(tile_t::from_quadkey(tiles[2], zoom) == tile_t{zoom, 2050, 2045}); + + CHECK(tile_t::from_quadkey(tiles[3], zoom) == tile_t{zoom, 2048, 2046}); + CHECK(tile_t::from_quadkey(tiles[6], zoom) == tile_t{zoom, 2050, 2046}); + + CHECK(tile_t::from_quadkey(tiles[4], zoom) == tile_t{zoom, 2048, 2047}); + CHECK(tile_t::from_quadkey(tiles[5], zoom) == tile_t{zoom, 2049, 2047}); + CHECK(tile_t::from_quadkey(tiles[7], zoom) == tile_t{zoom, 2050, 2047}); +} + +TEST_CASE("expire multipoint geometry", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + geom::point_t p1{0.0, 0.0}; + geom::point_t p2{15000.0, 15000.0}; + geom::multipoint_t mpt; + mpt.add_geometry(std::move(p1)); + mpt.add_geometry(std::move(p2)); + + SECTION("multipoint") { et.from_geometry(mpt); } + + SECTION("geom") + { + geom::geometry_t geom{std::move(mpt)}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{std::move(mpt)}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 5); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2047, 2047}); + CHECK(tile_t::from_quadkey(tiles[1], zoom) == tile_t{zoom, 2049, 2046}); + CHECK(tile_t::from_quadkey(tiles[2], zoom) == tile_t{zoom, 2048, 2047}); + CHECK(tile_t::from_quadkey(tiles[3], zoom) == tile_t{zoom, 2047, 2048}); + CHECK(tile_t::from_quadkey(tiles[4], zoom) == tile_t{zoom, 2048, 2048}); +} + +TEST_CASE("expire multilinestring geometry", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + geom::linestring_t l1{{2000.0, 2000.0}, {3000.0, 3000.0}}; + geom::linestring_t l2{{15000.0, 15000.0}, {25000.0, 15000.0}}; + geom::multilinestring_t ml; + ml.add_geometry(std::move(l1)); + ml.add_geometry(std::move(l2)); + + SECTION("multilinestring") { et.from_geometry(ml); } + + SECTION("geom") + { + geom::geometry_t geom{std::move(ml)}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{std::move(ml)}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 3); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2049, 2046}); + CHECK(tile_t::from_quadkey(tiles[1], zoom) == tile_t{zoom, 2048, 2047}); + CHECK(tile_t::from_quadkey(tiles[2], zoom) == tile_t{zoom, 2050, 2046}); +} + +TEST_CASE("expire multipolygon geometry", "[NoDB]") +{ + expire_tiles et{zoom, 10000, defproj}; + + geom::polygon_t p1{{{2000.0, 2000.0}, + {2000.0, 3000.0}, + {3000.0, 3000.0}, + {3000.0, 2000.0}, + {2000.0, 2000.0}}}; + + geom::polygon_t p2{{{15000.0, 15000.0}, + {45000.0, 15000.0}, + {45000.0, 45000.0}, + {15000.0, 45000.0}, + {15000.0, 15000.0}}}; + p2.add_inner_ring({{25000.0, 25000.0}, + {25000.0, 35000.0}, + {35000.0, 35000.0}, + {35000.0, 25000.0}, + {25000.0, 25000.0}}); + + geom::multipolygon_t mp; + mp.add_geometry(std::move(p1)); + mp.add_geometry(std::move(p2)); + + SECTION("multilinestring") { et.from_geometry(mp); } + + SECTION("geom") + { + geom::geometry_t geom{std::move(mp)}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{std::move(mp)}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 17); + + std::set result; + for (auto const &tile : tiles) { + result.insert(tile); + } + + std::set expected; + expected.insert(tile_t{zoom, 2048, 2047}.quadkey()); // p1 + + for (uint32_t x = 2049; x <= 2052; ++x) { + for (uint32_t y = 2043; y <= 2046; ++y) { + expected.insert(tile_t{zoom, x, y}.quadkey()); // p2 + } + } + REQUIRE(expected == result); +} + +TEST_CASE("expire geometry collection", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + geom::collection_t collection; + collection.add_geometry(geom::geometry_t{geom::point_t{0.0, 0.0}}); + collection.add_geometry(geom::geometry_t{ + geom::linestring_t{{15000.0, 15000.0}, {25000.0, 15000.0}}}); + + SECTION("geom") + { + geom::geometry_t geom{std::move(collection)}; + et.from_geometry(geom); + } + + SECTION("geom with check") + { + geom::geometry_t geom{std::move(collection)}; + geom.set_srid(3857); + et.from_geometry_if_3857(geom); + } + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.size() == 6); + CHECK(tile_t::from_quadkey(tiles[0], zoom) == tile_t{zoom, 2047, 2047}); + CHECK(tile_t::from_quadkey(tiles[1], zoom) == tile_t{zoom, 2049, 2046}); + CHECK(tile_t::from_quadkey(tiles[2], zoom) == tile_t{zoom, 2048, 2047}); + CHECK(tile_t::from_quadkey(tiles[3], zoom) == tile_t{zoom, 2050, 2046}); + CHECK(tile_t::from_quadkey(tiles[4], zoom) == tile_t{zoom, 2047, 2048}); + CHECK(tile_t::from_quadkey(tiles[5], zoom) == tile_t{zoom, 2048, 2048}); +} + +TEST_CASE("expire doesn't do anything if not in 3857", "[NoDB]") +{ + expire_tiles et{zoom, 20000, defproj}; + + geom::geometry_t geom{geom::point_t{0.0, 0.0}}; + geom.set_srid(1234); + et.from_geometry_if_3857(geom); + + auto const tiles = et.get_tiles(); + REQUIRE(tiles.empty()); +}