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()); +}