From 9b19ab7888e5e1c2ec77be5aa46974d4b10c24a3 Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Fri, 1 Sep 2017 16:33:35 -0700 Subject: [PATCH] Fast tileCount with help from @mapbox/sphericalmercator module --- include/mbgl/storage/offline.hpp | 4 ++- include/mbgl/util/projection.hpp | 22 ++++++++++--- platform/default/mbgl/storage/offline.cpp | 33 ++++++++++++++----- .../default/mbgl/storage/offline_download.cpp | 4 +-- src/mbgl/util/tile_cover.cpp | 21 ++++++++++++ src/mbgl/util/tile_cover.hpp | 3 ++ test/storage/offline.test.cpp | 8 +++++ test/util/tile_cover.test.cpp | 9 +++++ 8 files changed, 89 insertions(+), 15 deletions(-) diff --git a/include/mbgl/storage/offline.hpp b/include/mbgl/storage/offline.hpp index 818cfe2ba5c..afb2fa1e81a 100644 --- a/include/mbgl/storage/offline.hpp +++ b/include/mbgl/storage/offline.hpp @@ -31,12 +31,14 @@ class OfflineTilePyramidRegionDefinition { /* Private */ std::vector tileCover(SourceType, uint16_t tileSize, const Range& zoomRange) const; - + unsigned long tileCount(SourceType, uint16_t tileSize, const Range& zoomRange) const; const std::string styleURL; const LatLngBounds bounds; const double minZoom; const double maxZoom; const float pixelRatio; +private: + Range coveringZoomRange(SourceType, uint16_t tileSize, const Range& zoomRange) const; }; /* diff --git a/include/mbgl/util/projection.hpp b/include/mbgl/util/projection.hpp index 3cc11465137..f64502c5bc6 100644 --- a/include/mbgl/util/projection.hpp +++ b/include/mbgl/util/projection.hpp @@ -75,10 +75,7 @@ class Projection { } static Point project(const LatLng& latLng, double scale) { - return Point { - util::LONGITUDE_MAX + latLng.longitude(), - util::LONGITUDE_MAX - util::RAD2DEG * std::log(std::tan(M_PI / 4 + latLng.latitude() * M_PI / util::DEGREES_MAX)) - } * worldSize(scale) / util::DEGREES_MAX; + return project_(latLng, worldSize(scale)); } static LatLng unproject(const Point& p, double scale, LatLng::WrapMode wrapMode = LatLng::Unwrapped) { @@ -89,6 +86,23 @@ class Projection { wrapMode }; } + + // Project lat, lon to point in a zoom-dependent world size + static Point project(const LatLng& point, uint8_t zoom, uint16_t tileSize) { + const double t2z = tileSize * std::pow(2, zoom); + Point pt = project_(point, t2z); + // Flip y coordinate + auto x = std::round(std::min(pt.x, t2z)); + auto y = std::round(std::min(t2z - pt.y, t2z)); + return { x, y }; + } +private: + static Point project_(const LatLng& latLng, double worldSize) { + return Point { + util::LONGITUDE_MAX + latLng.longitude(), + util::LONGITUDE_MAX - util::RAD2DEG * std::log(std::tan(M_PI / 4 + latLng.latitude() * M_PI / util::DEGREES_MAX)) + } * worldSize / util::DEGREES_MAX; + } }; } // namespace mbgl diff --git a/platform/default/mbgl/storage/offline.cpp b/platform/default/mbgl/storage/offline.cpp index fd2d47819b4..644684c8a68 100644 --- a/platform/default/mbgl/storage/offline.cpp +++ b/platform/default/mbgl/storage/offline.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -24,17 +25,11 @@ OfflineTilePyramidRegionDefinition::OfflineTilePyramidRegionDefinition( } std::vector OfflineTilePyramidRegionDefinition::tileCover(SourceType type, uint16_t tileSize, const Range& zoomRange) const { - double minZ = std::max(util::coveringZoomLevel(minZoom, type, tileSize), zoomRange.min); - double maxZ = std::min(util::coveringZoomLevel(maxZoom, type, tileSize), zoomRange.max); - - assert(minZ >= 0); - assert(maxZ >= 0); - assert(minZ < std::numeric_limits::max()); - assert(maxZ < std::numeric_limits::max()); + const Range clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange); std::vector result; - for (uint8_t z = minZ; z <= maxZ; z++) { + for (uint8_t z = clampedZoomRange.min; z <= clampedZoomRange.max; z++) { for (const auto& tile : util::tileCover(bounds, z)) { result.emplace_back(tile.canonical); } @@ -43,6 +38,28 @@ std::vector OfflineTilePyramidRegionDefinition::tileCover(Sourc return result; } +unsigned long OfflineTilePyramidRegionDefinition::tileCount(SourceType type, uint16_t tileSize, const Range& zoomRange) const { + + const Range clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange); + unsigned long result = 0;; + for (uint8_t z = clampedZoomRange.min; z <= clampedZoomRange.max; z++) { + result += util::tileCount(bounds, z, tileSize); + } + + return result; +} + +Range OfflineTilePyramidRegionDefinition::coveringZoomRange(SourceType type, uint16_t tileSize, const Range& zoomRange) const { + double minZ = std::max(util::coveringZoomLevel(minZoom, type, tileSize), zoomRange.min); + double maxZ = std::min(util::coveringZoomLevel(maxZoom, type, tileSize), zoomRange.max); + + assert(minZ >= 0); + assert(maxZ >= 0); + assert(minZ < std::numeric_limits::max()); + assert(maxZ < std::numeric_limits::max()); + return { static_cast(minZ), static_cast(maxZ) }; +} + OfflineRegionDefinition decodeOfflineRegionDefinition(const std::string& region) { rapidjson::GenericDocument, rapidjson::CrtAllocator> doc; doc.Parse<0>(region.c_str()); diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index 7f0001f64bd..ff61114888a 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -80,7 +80,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { auto handleTiledSource = [&] (const variant& urlOrTileset, const uint16_t tileSize) { if (urlOrTileset.is()) { result.requiredResourceCount += - definition.tileCover(type, tileSize, urlOrTileset.get().zoomRange).size(); + definition.tileCount(type, tileSize, urlOrTileset.get().zoomRange); } else { result.requiredResourceCount += 1; const auto& url = urlOrTileset.get(); @@ -90,7 +90,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { optional tileset = style::conversion::convertJSON(*sourceResponse->data, error); if (tileset) { result.requiredResourceCount += - definition.tileCover(type, tileSize, (*tileset).zoomRange).size(); + definition.tileCount(type, tileSize, (*tileset).zoomRange); } } else { result.requiredResourceCountIsPrecise = false; diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index b53e91162c1..c06634c9b23 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -169,5 +169,26 @@ std::vector tileCover(const TransformState& state, int32_t z) { z); } +// Taken from https://github.com/mapbox/sphericalmercator#xyzbbox-zoom-tms_style-srs +// Computes the projected tiles for the lower left and uppoer right points of the bounds +// and uses that to compute the tile cover count +unsigned long tileCount(const LatLngBounds& bounds, uint8_t zoom, uint16_t tileSize_){ + + auto sw = Projection::project(bounds.southwest().wrapped(), zoom, tileSize_); + auto ne = Projection::project(bounds.northeast().wrapped(), zoom, tileSize_); + + auto x1 = floor(sw.x/ tileSize_); + auto x2 = floor((ne.x - 1) / tileSize_); + auto y1 = floor(sw.y/ tileSize_); + auto y2 = floor((ne.y - 1) / tileSize_); + + auto minX = std::fmax(std::min(x1, x2), 0); + auto maxX = std::max(x1, x2); + auto minY = (std::pow(2, zoom) - 1) - std::max(y1, y2); + auto maxY = (std::pow(2, zoom) - 1) - std::fmax(std::min(y1, y2), 0); + + return (maxX - minX + 1) * (maxY - minY + 1); +} + } // namespace util } // namespace mbgl diff --git a/src/mbgl/util/tile_cover.hpp b/src/mbgl/util/tile_cover.hpp index 2d32d8bf41f..405e6a48e6b 100644 --- a/src/mbgl/util/tile_cover.hpp +++ b/src/mbgl/util/tile_cover.hpp @@ -18,5 +18,8 @@ int32_t coveringZoomLevel(double z, SourceType type, uint16_t tileSize); std::vector tileCover(const TransformState&, int32_t z); std::vector tileCover(const LatLngBounds&, int32_t z); +// Compute only the count of tiles needed for tileCover +unsigned long tileCount(const LatLngBounds&, uint8_t z, uint16_t tileSize); + } // namespace util } // namespace mbgl diff --git a/test/storage/offline.test.cpp b/test/storage/offline.test.cpp index 0faaabc298e..e7ebe5199fa 100644 --- a/test/storage/offline.test.cpp +++ b/test/storage/offline.test.cpp @@ -52,3 +52,11 @@ TEST(OfflineTilePyramidRegionDefinition, TileCoverWrapped) { EXPECT_EQ((std::vector{ { 0, 0, 0 } }), region.tileCover(SourceType::Vector, 512, { 0, 22 })); } + +TEST(OfflineTilePyramidRegionDefinition, TileCount) { + OfflineTilePyramidRegionDefinition region("", sanFranciscoWrapped, 0, 22, 1.0); + + //These numbers match the count from tileCover().size(). + EXPECT_EQ(38424u, region.tileCount(SourceType::Vector, 512, { 10, 18 })); + EXPECT_EQ(9675240u, region.tileCount(SourceType::Vector, 512, { 3, 22 })); +} diff --git a/test/util/tile_cover.test.cpp b/test/util/tile_cover.test.cpp index c746e6dab5e..933c18b5ea6 100644 --- a/test/util/tile_cover.test.cpp +++ b/test/util/tile_cover.test.cpp @@ -84,3 +84,12 @@ TEST(TileCover, SanFranciscoZ0Wrapped) { EXPECT_EQ((std::vector{ { 0, 1, 0 } }), util::tileCover(sanFranciscoWrapped, 0)); } + +TEST(TileCount, SanFranciscoZ10) { + EXPECT_EQ(4u, util::tileCount(sanFrancisco, 10, util::tileSize)); +} + +TEST(TileCount, SanFranciscoZ22) { + EXPECT_EQ(7254450u, util::tileCount(sanFrancisco, 22, util::tileSize)); +} +