Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Tile coverage updates #12310

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/mbgl/util/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ constexpr double EARTH_RADIUS_M = 6378137;
constexpr double LATITUDE_MAX = 85.051128779806604;
constexpr double LONGITUDE_MAX = 180;
constexpr double DEGREES_MAX = 360;
constexpr double PITCH_MAX = M_PI / 3;
constexpr double PITCH_MAX = 67.5 * DEG2RAD;
constexpr double MIN_ZOOM = 0.0;
constexpr double MAX_ZOOM = 25.5;
constexpr float MIN_ZOOM_F = MIN_ZOOM;
Expand Down
2 changes: 1 addition & 1 deletion platform/glfw/glfw_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action,
double easing = bearing - routeMap->getBearing();
easing += easing > 180.0 ? -360.0 : easing < -180 ? 360.0 : 0;
routeMap->setBearing(routeMap->getBearing() + (easing / 20));
routeMap->setPitch(60.0);
routeMap->setPitch(67.5);
routeMap->setZoom(18.0);
};
view->animateRouteCallback(view->map);
Expand Down
43 changes: 27 additions & 16 deletions src/mbgl/renderer/tile_pyramid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,24 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
handleWrapJump(parameters.transformState.getLatLng().longitude());

// Determine the overzooming/underzooming amounts and required tiles.
int32_t overscaledZoom = util::coveringZoomLevel(parameters.transformState.getZoom(), type, tileSize);
int32_t tileZoom = overscaledZoom;
int32_t panZoom = zoomRange.max;
uint8_t overscaledZoom = util::coveringZoomLevel(parameters.transformState.getZoom(), type, tileSize);
uint8_t tileZoom = overscaledZoom;
uint8_t panZoom = zoomRange.max;

std::vector<UnwrappedTileID> idealTiles;
std::vector<UnwrappedTileID> panTiles;

bool usePanLayers = true;
std::vector<Immutable<style::Layer::Impl>> panLayers;
panLayers.reserve(layers.size());
for (const auto& layer : layers) {
if (layer->type == LayerType::Symbol)
continue;
panLayers.emplace_back(layer);
}

if (overscaledZoom >= zoomRange.min) {
int32_t idealZoom = std::min<int32_t>(zoomRange.max, overscaledZoom);
uint8_t idealZoom = std::min(zoomRange.max, overscaledZoom);


// Make sure we're not reparsing overzoomed raster tiles.
Expand All @@ -114,31 +123,34 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
// Request lower zoom level tiles (if configured to do so) in an attempt
// to show something on the screen faster at the cost of a little of bandwidth.
if (parameters.prefetchZoomDelta) {
panZoom = std::max<int32_t>(tileZoom - parameters.prefetchZoomDelta, zoomRange.min);
panZoom = util::clamp(
util::min(uint8_t(tileZoom - parameters.prefetchZoomDelta), tileZoom),
zoomRange.min,
util::min(uint8_t(zoomRange.max - parameters.prefetchZoomDelta), zoomRange.max));
}

if (panZoom < idealZoom) {
panTiles = util::tileCover(parameters.transformState, panZoom);
panTiles = util::tileCover(parameters.transformState, panZoom, util::TileCoverMode::Full);
}
}

idealTiles = util::tileCover(parameters.transformState, idealZoom);
idealTiles = util::tileCover(parameters.transformState, idealZoom,
panTiles.empty() ? util::TileCoverMode::Full : util::TileCoverMode::Limited5x5);
}

// Stores a list of all the tiles that we're definitely going to retain. There are two
// kinds of tiles we need: the ideal tiles determined by the tile cover. They may not yet be in
// use because they're still loading. In addition to that, we also need to retain all tiles that
// we're actively using, e.g. as a replacement for tile that aren't loaded yet.
std::set<OverscaledTileID> retain;
std::set<UnwrappedTileID> rendered;

auto retainTileFn = [&](Tile& tile, TileNecessity necessity) -> void {
if (retain.emplace(tile.id).second) {
tile.setNecessity(necessity);
}

if (needsRelayout) {
tile.setLayers(layers);
tile.setLayers(usePanLayers ? panLayers : layers);
}
};
auto getTileFn = [&](const OverscaledTileID& tileID) -> Tile* {
Expand All @@ -151,7 +163,7 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
// tiles are used from the cache, but not created.
optional<util::TileRange> tileRange = {};
if (bounds) {
tileRange = util::TileRange::fromLatLngBounds(*bounds, zoomRange.min, std::min(tileZoom, (int32_t)zoomRange.max));
tileRange = util::TileRange::fromLatLngBounds(*bounds, zoomRange.min, std::min(tileZoom, zoomRange.max));
}
auto createTileFn = [&](const OverscaledTileID& tileID) -> Tile* {
if (tileRange && !tileRange->contains(tileID.canonical)) {
Expand All @@ -162,7 +174,7 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
tile = createTile(tileID);
if (tile) {
tile->setObserver(observer);
tile->setLayers(layers);
tile->setLayers(usePanLayers ? panLayers : layers);
}
}
if (!tile) {
Expand All @@ -178,18 +190,18 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer

auto renderTileFn = [&](const UnwrappedTileID& tileID, Tile& tile) {
renderTiles.emplace_back(tileID, tile);
rendered.emplace(tileID);
previouslyRenderedTiles.erase(tileID); // Still rendering this tile, no need for special fading logic.
tile.markRenderedIdeal();
if (!usePanLayers) tile.markRenderedIdeal();
};

renderTiles.clear();

if (!panTiles.empty()) {
algorithm::updateRenderables(getTileFn, createTileFn, retainTileFn,
[](const UnwrappedTileID&, Tile&) {}, panTiles, zoomRange, panZoom);
algorithm::updateRenderables(getTileFn, createTileFn, retainTileFn, renderTileFn,
panTiles, zoomRange, panZoom);
}

usePanLayers = false;
algorithm::updateRenderables(getTileFn, createTileFn, retainTileFn, renderTileFn,
idealTiles, zoomRange, tileZoom);

Expand All @@ -201,7 +213,6 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
// Don't mark the tile "Required" to avoid triggering a new network request
retainTileFn(tile, TileNecessity::Optional);
renderTiles.emplace_back(previouslyRenderedTile.first, tile);
rendered.emplace(previouslyRenderedTile.first);
}
}

Expand Down
81 changes: 58 additions & 23 deletions src/mbgl/util/tile_cover.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <mbgl/util/tile_cover.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/interpolate.hpp>
#include <mbgl/util/math.hpp>
#include <mbgl/map/mode.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/util/tile_cover_impl.hpp>
#include <mbgl/util/tile_coordinate.hpp>
Expand Down Expand Up @@ -78,13 +80,15 @@ namespace util {

namespace {

std::vector<UnwrappedTileID> tileCover(const Point<double>& tl,
const Point<double>& tr,
const Point<double>& br,
const Point<double>& bl,
std::vector<UnwrappedTileID> tileCover(Point<double> tl,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: non-uniform input arguments.

Would it be better to keep these as const references, and conditionally return a clamped point from limitTileCoordinate?

Point<double> tr,
Point<double> br,
Point<double> bl,
const Point<double>& c,
int32_t z) {
const int32_t tiles = 1 << z;
uint8_t z,
TileCoverMode mode,
double bearing = 0) {
const int32_t maxTilesPerAxis = 1 << z;

struct ID {
int32_t x, y;
Expand All @@ -93,22 +97,51 @@ std::vector<UnwrappedTileID> tileCover(const Point<double>& tl,

std::vector<ID> t;

// Rotate the center according to bearing.
const Point<double> rotatedCenter = util::rotate(c, bearing);

// Limits the tile coverage to an axis-aligned rectangle according to the
// given bearing.
auto limitTileCoordinate = [&](Point<double>& point) {
auto clampAxis = [](double a, double b) -> double {
const double min = a >= b ? b : b - 2.0;
const double max = a >= b ? b + 2.0 : b;
return util::clamp(a, min, max);
};

// Rotate the tile coordinate according to bearing.
Point<double> rotated = util::rotate(point, bearing);
rotated.x = clampAxis(rotated.x, rotatedCenter.x);
rotated.y = clampAxis(rotated.y, rotatedCenter.y);

// After clamping, rotate back to original value.
point = util::rotate(rotated, -bearing);
};

// Limit tile coverage in continuous mode.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment still appropriate? How is continuous mode being checked here?

if (mode == TileCoverMode::Limited5x5) {
limitTileCoordinate(tl);
limitTileCoordinate(tr);
limitTileCoordinate(br);
limitTileCoordinate(bl);
}

auto scanLine = [&](int32_t x0, int32_t x1, int32_t y) {
int32_t x;
if (y >= 0 && y <= tiles) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition is no longer necessary? I think it is necessary at the poles.

for (x = x0; x < x1; ++x) {
const auto dx = x + 0.5 - c.x, dy = y + 0.5 - c.y;
t.emplace_back(ID{ x, y, dx * dx + dy * dy });
}
double dx, dy;
for (int32_t x = x0; x < x1; ++x) {
// We don't know which corner of the tile coordinate is the closest,
// so add 0.5 to calculate the distance from its tile center.
dx = x + 0.5 - c.x; dy = y + 0.5 - c.y;
t.emplace_back(ID{ x, y, dx * dx + dy * dy });
}
};

// Divide the screen up in two triangles and scan each of them:
// \---+
// | \ |
// +---\.
scanTriangle(tl, tr, br, 0, tiles, scanLine);
scanTriangle(br, bl, tl, 0, tiles, scanLine);
scanTriangle(tl, tr, br, 0, maxTilesPerAxis, scanLine);
scanTriangle(br, bl, tl, 0, maxTilesPerAxis, scanLine);

// Sort first by distance, then by x/y.
std::sort(t.begin(), t.end(), [](const ID& a, const ID& b) {
Expand All @@ -129,7 +162,7 @@ std::vector<UnwrappedTileID> tileCover(const Point<double>& tl,

} // namespace

int32_t coveringZoomLevel(double zoom, style::SourceType type, uint16_t size) {
uint8_t coveringZoomLevel(double zoom, style::SourceType type, uint16_t size) {
zoom += ::log2(util::tileSize / size);
if (type == style::SourceType::Raster || type == style::SourceType::Video) {
return ::round(zoom);
Expand All @@ -138,7 +171,7 @@ int32_t coveringZoomLevel(double zoom, style::SourceType type, uint16_t size) {
}
}

std::vector<UnwrappedTileID> tileCover(const LatLngBounds& bounds_, int32_t z) {
std::vector<UnwrappedTileID> tileCover(const LatLngBounds& bounds_, uint8_t z) {
if (bounds_.isEmpty() ||
bounds_.south() > util::LATITUDE_MAX ||
bounds_.north() < -util::LATITUDE_MAX) {
Expand All @@ -155,24 +188,26 @@ std::vector<UnwrappedTileID> tileCover(const LatLngBounds& bounds_, int32_t z) {
Projection::project(bounds.southeast(), z),
Projection::project(bounds.southwest(), z),
Projection::project(bounds.center(), z),
z);
z, TileCoverMode::Full);
}

std::vector<UnwrappedTileID> tileCover(const TransformState& state, int32_t z) {
std::vector<UnwrappedTileID> tileCover(const TransformState& state, uint8_t z, TileCoverMode mode) {
assert(state.valid());

const double w = state.getSize().width;
const double h = state.getSize().height;

// top-left, top-right, bottom-right, bottom-left, center
return tileCover(
TileCoordinate::fromScreenCoordinate(state, z, { 0, 0 }).p,
TileCoordinate::fromScreenCoordinate(state, z, { w, 0 }).p,
TileCoordinate::fromScreenCoordinate(state, z, { w, h }).p,
TileCoordinate::fromScreenCoordinate(state, z, { 0, h }).p,
TileCoordinate::fromScreenCoordinate(state, z, { w/2, h/2 }).p,
z);
z, mode, state.getAngle());
}

std::vector<UnwrappedTileID> tileCover(const Geometry<double>& geometry, int32_t z) {
std::vector<UnwrappedTileID> tileCover(const Geometry<double>& geometry, uint8_t z) {
std::vector<UnwrappedTileID> result;
TileCover tc(geometry, z, true);
while (tc.hasNext()) {
Expand All @@ -182,7 +217,7 @@ std::vector<UnwrappedTileID> tileCover(const Geometry<double>& geometry, int32_t
return result;
}

// Taken from https://github.com/mapbox/sphericalmercator#xyzbbox-zoom-tms_style-srs
// Taken from https://github.com/mapbox/sphericalmercator#xyzbbox-z-tms_style-srs
// Computes the projected tiles for the lower left and upper right points of the bounds
// and uses that to compute the tile cover count
uint64_t tileCount(const LatLngBounds& bounds, uint8_t zoom){
Expand Down Expand Up @@ -212,7 +247,7 @@ uint64_t tileCount(const Geometry<double>& geometry, uint8_t z) {
return tileCount;
}

TileCover::TileCover(const LatLngBounds&bounds_, int32_t z) {
TileCover::TileCover(const LatLngBounds&bounds_, uint8_t z) {
LatLngBounds bounds = LatLngBounds::hull(
{ std::max(bounds_.south(), -util::LATITUDE_MAX), bounds_.west() },
{ std::min(bounds_.north(), util::LATITUDE_MAX), bounds_.east() });
Expand All @@ -232,7 +267,7 @@ TileCover::TileCover(const LatLngBounds&bounds_, int32_t z) {
impl = std::make_unique<TileCover::Impl>(z, p, false);
}

TileCover::TileCover(const Geometry<double>& geom, int32_t z, bool project/* = true*/)
TileCover::TileCover(const Geometry<double>& geom, uint8_t z, bool project/* = true*/)
: impl( std::make_unique<TileCover::Impl>(z, geom, project)) {
}

Expand Down
18 changes: 12 additions & 6 deletions src/mbgl/util/tile_cover.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ namespace util {
// Helper class to stream tile-cover results per row
class TileCover {
public:
TileCover(const LatLngBounds&, int32_t z);
TileCover(const LatLngBounds&, uint8_t z);
// When project == true, projects the geometry points to tile coordinates
TileCover(const Geometry<double>&, int32_t z, bool project = true);
TileCover(const Geometry<double>&, uint8_t z, bool project = true);
~TileCover();

optional<UnwrappedTileID> next();
Expand All @@ -31,11 +31,17 @@ class TileCover {
std::unique_ptr<Impl> impl;
};

int32_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize);
enum class TileCoverMode : uint32_t {
Full, // Full tile coverage
Limited5x5, // Limited tile coverage to a viewport axis-aligned 5x5 matrix
// Pyramid // TODO: Tile pyramid containing multiple tile zoom levels
};

uint8_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize);

std::vector<UnwrappedTileID> tileCover(const TransformState&, int32_t z);
std::vector<UnwrappedTileID> tileCover(const LatLngBounds&, int32_t z);
std::vector<UnwrappedTileID> tileCover(const Geometry<double>&, int32_t z);
std::vector<UnwrappedTileID> tileCover(const TransformState&, uint8_t z, TileCoverMode = TileCoverMode::Full);
std::vector<UnwrappedTileID> tileCover(const LatLngBounds&, uint8_t z);
std::vector<UnwrappedTileID> tileCover(const Geometry<double>&, uint8_t z);

// Compute only the count of tiles needed for tileCover
uint64_t tileCount(const LatLngBounds&, uint8_t z);
Expand Down
2 changes: 1 addition & 1 deletion test/map/transform.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ TEST(Transform, PitchBounds) {

ASSERT_DOUBLE_EQ(transform.getState().getPitch() * util::RAD2DEG, 0.0);
ASSERT_DOUBLE_EQ(transform.getState().getMinPitch() * util::RAD2DEG, 0.0);
ASSERT_DOUBLE_EQ(transform.getState().getMaxPitch() * util::RAD2DEG, 60.0);
ASSERT_NEAR(transform.getState().getMaxPitch() * util::RAD2DEG, 67.5, 1e-5);

transform.setMinPitch(45.0 * util::DEG2RAD);
transform.setPitch(0.0 * util::DEG2RAD);
Expand Down
18 changes: 18 additions & 0 deletions test/util/tile_cover.test.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <mbgl/util/tile_cover.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/map/transform.hpp>
#include <mbgl/util/tile_coordinate.hpp>
#include <mbgl/util/math.hpp>

#include <algorithm>
#include <stdlib.h> /* srand, rand */
Expand Down Expand Up @@ -43,6 +45,22 @@ TEST(TileCover, Pitch) {
{ 2, 1, 2 }, { 2, 1, 1 }, { 2, 2, 2 }, { 2, 2, 1 }, { 2, 3, 2 }
}),
util::tileCover(transform.getState(), 2));

// 2 tiles on the left, 1 center tiler, then 2 tiles on the right.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break this into a separate test

constexpr uint32_t maximumLimitedTiles = 5 * 5;

transform.resize({ 2048, 2048 });
transform.setAngle(-45.0 * M_PI / 180.0);

transform.setPitch(67.5 * M_PI / 180.0);
for (double zoom = 0.0; zoom < 16.0; zoom += 0.5) {
uint8_t integerZoom = std::floor(zoom);
transform.setZoom(zoom);
auto fullTiles = util::tileCover(transform.getState(), integerZoom, util::TileCoverMode::Full);
auto limitedTiles = util::tileCover(transform.getState(), integerZoom, util::TileCoverMode::Limited5x5);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to also check the correctness of the returned tiles as part of the test.

EXPECT_GT(fullTiles.size(), limitedTiles.size());
EXPECT_GE(maximumLimitedTiles, limitedTiles.size());
}
}

TEST(TileCover, WorldZ1) {
Expand Down