From b12889539e9e92a48360e536d11abf95b6513690 Mon Sep 17 00:00:00 2001 From: Aleksandar Stojiljkovic Date: Fri, 26 Jul 2019 09:17:46 +0300 Subject: [PATCH 1/2] Tiles LOD: load and render based on zoom when displayed Fixes #9037 --- include/mbgl/util/constants.hpp | 8 ++++- src/mbgl/map/transform_state.cpp | 8 ++--- src/mbgl/map/transform_state.hpp | 8 ++--- src/mbgl/renderer/tile_pyramid.cpp | 4 +-- src/mbgl/util/tile_cover.cpp | 53 ++++++++++++++++++++++++++++++ src/mbgl/util/tile_cover.hpp | 1 + 6 files changed, 71 insertions(+), 11 deletions(-) diff --git a/include/mbgl/util/constants.hpp b/include/mbgl/util/constants.hpp index ffdc4b2b300..6633e253238 100644 --- a/include/mbgl/util/constants.hpp +++ b/include/mbgl/util/constants.hpp @@ -34,7 +34,13 @@ constexpr double LATITUDE_MAX = 85.051128779806604; constexpr double LONGITUDE_MAX = 180; constexpr double DEGREES_MAX = 360; constexpr double PITCH_MIN = 0.0; -constexpr double PITCH_MAX = M_PI / 3; +/* + * Max pitch is limited to 90 deg - TransformState::fov / 2, which evaluates to 71.5 deg, when + * perspective center's offset is 0. When padding is used, and the perspective center moved from + * center of the screen, max pitch is further capped by Transform::getMaxPitchForEdgeInsets. + * We set the max to 70 to keep the limit constant with small padding. + */ +constexpr double PITCH_MAX = 70 * DEG2RAD; constexpr double MIN_ZOOM = 0.0; constexpr double MAX_ZOOM = 25.5; constexpr float MIN_ZOOM_F = MIN_ZOOM; diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 77309a2a55b..31d95821f12 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -174,6 +174,10 @@ double TransformState::pixel_y() const { return center + y; } +ScreenCoordinate TransformState::getCenterOffset() const { + return { 0.5 * (edgeInsets.left() - edgeInsets.right()), 0.5 * (edgeInsets.top() - edgeInsets.bottom()) }; +} + #pragma mark - Zoom double TransformState::getZoom() const { @@ -373,10 +377,6 @@ void TransformState::constrain(double& scale_, double& x_, double& y_) const { } } -ScreenCoordinate TransformState::getCenterOffset() const { - return { 0.5 * (edgeInsets.left() - edgeInsets.right()), 0.5 * (edgeInsets.top() - edgeInsets.bottom()) }; -} - void TransformState::moveLatLng(const LatLng& latLng, const ScreenCoordinate& anchor) { auto centerCoord = Projection::project(getLatLng(LatLng::Unwrapped), scale); auto latLngCoord = Projection::project(latLng, scale); diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index cca42db20f6..3ec1d52d20b 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -49,6 +49,10 @@ class TransformState { double pixel_x() const; double pixel_y() const; + // Viewport center offset, from [size.width / 2, size.height / 2], defined + // by |edgeInsets| in screen coordinates, with top left origin. + ScreenCoordinate getCenterOffset() const; + // Zoom double getZoom() const; uint8_t getIntegerZoom() const; @@ -96,10 +100,6 @@ class TransformState { bool rotatedNorth() const; void constrain(double& scale, double& x, double& y) const; - // Viewport center offset, from [size.width / 2, size.height / 2], defined - // by |edgeInsets| in screen coordinates, with top left origin. - ScreenCoordinate getCenterOffset() const; - LatLngBounds bounds; // Limit the amount of zooming possible on the map. diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 54e0b1eb26a..64eb5f2a7b7 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -106,11 +106,11 @@ void TilePyramid::update(const std::vector>& l } if (panZoom < idealZoom) { - panTiles = util::tileCover(parameters.transformState, panZoom); + panTiles = util::tileCoverWithLOD(parameters.transformState, panZoom, zoomRange.min); } } - idealTiles = util::tileCover(parameters.transformState, idealZoom); + idealTiles = util::tileCoverWithLOD(parameters.transformState, idealZoom, zoomRange.min); } // Stores a list of all the tiles that we're definitely going to retain. There are two diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index 5189b79f267..11e1a97f4c3 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -173,6 +173,59 @@ std::vector tileCover(const TransformState& state, int32_t z) { z); } +std::vector tileCoverWithLOD(const TransformState& state, int32_t z, int32_t minZ) { + assert(state.valid()); + + const double w = state.getSize().width; + const double h = state.getSize().height; + + const auto offset = state.getCenterOffset(); + constexpr double zoomDiff = 1.0; + constexpr double coefLOD[] = { + 0.55 * zoomDiff / (zoomDiff + 1), + 0.55 * (zoomDiff + 1) / (zoomDiff + 2), + 0.55 * (zoomDiff + 2) / (zoomDiff + 3) + }; + // Tangens of field of view above center. + const double tanFov = (h * 0.5 + offset.y) / (1.5 * h); + + std::vector result; + double top = 0.0; + double bottom = 0.0; + + for (size_t i = 0; top < h && i <= std::extent::value; i++, z--) { + if (z == minZ || i == std::extent::value) { + top = h; // final pass, get all to the top. + } else { + const double treshold = state.getPitch() ? : (h * 0.5 + offset.y); + top = std::min(h, treshold ); + top = state.getPitch() + ? std::min(h, h * 0.5 - offset.y + h * coefLOD[i] / (tanFov * std::tan(state.getPitch()))) + : h; + } + std::vector cover = tileCover( + TileCoordinate::fromScreenCoordinate(state, z, { 0, top }).p, + TileCoordinate::fromScreenCoordinate(state, z, { w, top }).p, + TileCoordinate::fromScreenCoordinate(state, z, { w, bottom }).p, + TileCoordinate::fromScreenCoordinate(state, z, { 0, bottom }).p, + TileCoordinate::fromScreenCoordinate(state, z, { w/2, h/2 }).p, + z); + bottom = top; + if (i == 0) { + if (top == h) { + return cover; + } + std::swap(result, cover); + continue; + } + result.insert( + result.end(), + std::make_move_iterator(cover.begin()), + std::make_move_iterator(cover.end())); + } + return result; +} + std::vector tileCover(const Geometry& geometry, int32_t z) { std::vector result; TileCover tc(geometry, z, true); diff --git a/src/mbgl/util/tile_cover.hpp b/src/mbgl/util/tile_cover.hpp index c953d764d29..eb9e0bc325e 100644 --- a/src/mbgl/util/tile_cover.hpp +++ b/src/mbgl/util/tile_cover.hpp @@ -34,6 +34,7 @@ class TileCover { int32_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize); std::vector tileCover(const TransformState&, int32_t z); +std::vector tileCoverWithLOD(const TransformState&, int32_t z, int32_t minZLOD); std::vector tileCover(const LatLngBounds&, int32_t z); std::vector tileCover(const Geometry&, int32_t z); From 667a08f6fc19e7b9ca679298ad33f1986e93efd9 Mon Sep 17 00:00:00 2001 From: Aleksandar Stojiljkovic Date: Mon, 5 Aug 2019 18:16:26 +0300 Subject: [PATCH 2/2] [core] Tile cover LOD: lod covers single level tile cover Unit test verifies that, with different camera parameters combinations LOD tile cover covers all tiles returned by single level tileCover. --- src/mbgl/util/tile_cover.cpp | 3 +++ test/util/tile_cover.test.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index 11e1a97f4c3..32e76c4214b 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -181,6 +181,9 @@ std::vector tileCoverWithLOD(const TransformState& state, int32 const auto offset = state.getCenterOffset(); constexpr double zoomDiff = 1.0; + // Explanation on 0.55: mathematically, it is 0.5 used in calculation of + // the next LOD. 0.55 is chosen to avoid using LOD for less than 60 degrees + // pitch. constexpr double coefLOD[] = { 0.55 * zoomDiff / (zoomDiff + 1), 0.55 * (zoomDiff + 1) / (zoomDiff + 2), diff --git a/test/util/tile_cover.test.cpp b/test/util/tile_cover.test.cpp index e35e6e2e994..1bafc8b4ec4 100644 --- a/test/util/tile_cover.test.cpp +++ b/test/util/tile_cover.test.cpp @@ -78,6 +78,35 @@ TEST(TileCover, PitchWithLargerResultSet) { }), (std::vector { cover.begin(), cover.begin() + 16}) ); } +TEST(TileCover, TileCoverLODCoversSingleLevelTileCover) { + Transform transform; + transform.resize({ 512, 768 }); + + std::vector padding = { EdgeInsets { 0, 100, 0, 0 }, EdgeInsets { 800, 0, 0, 0 } }; + std::vector zoom = { 14, 22 }; + std::vector bearing = { 2, 45, -22.5 }; + std::vector pitch = { 0, 30, 90 }; + + for (auto pad : padding) { + for (auto z : zoom) { + for (auto bear : bearing) { + for (auto p : pitch) { + transform.jumpTo(CameraOptions().withCenter(LatLng { 0.1, -0.1 }) + .withPadding(pad).withZoom(z).withBearing(bear).withPitch(p)); + auto singleLevelCover = util::tileCover(transform.getState(), z); + auto lodTileCover = util::tileCoverWithLOD(transform.getState(), z, z / 10 * 10); + for (auto tile: singleLevelCover) { + EXPECT_NE(lodTileCover.cend(), std::find_if(lodTileCover.cbegin(), lodTileCover.cend(), + [&tile] (auto parent) { return tile == parent || tile.isChildOf(parent); })) << "for padding: [" + << pad.top() << ", " << pad.left() << ", 0, 0] zoom:" << z << " bearing:" + << bear << " and pitch:" << p; + } + } + } + } + } +} + TEST(TileCover, WorldZ1) { EXPECT_EQ((std::vector{ { 1, 0, 0 }, { 1, 0, 1 }, { 1, 1, 0 }, { 1, 1, 1 },