-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[core] Implement TileCover for polygonal regions #11267
Changes from 7 commits
7095b39
7bf6a35
af2c2ce
4bda046
7cabdc3
d2933ae
1a147fe
8c6534b
0c0a581
7d88d1a
1193859
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
#include <benchmark/benchmark.h> | ||
#include <mbgl/util/geo.hpp> | ||
#include <mbgl/util/geometry.hpp> | ||
#include <mbgl/util/tile_coordinate.hpp> | ||
#include <mbgl/util/tile_cover.hpp> | ||
#include <mbgl/map/transform.hpp> | ||
|
||
using namespace mbgl; | ||
|
||
static const LatLngBounds sanFrancisco = | ||
LatLngBounds::hull({ 37.6609, -122.5744 }, { 37.8271, -122.3204 }); | ||
|
||
static void TileCountBounds(benchmark::State& state) { | ||
std::size_t length = 0; | ||
while (state.KeepRunning()) { | ||
auto count = util::tileCount(sanFrancisco, 10); | ||
length += count; | ||
} | ||
} | ||
|
||
static void TileCoverPitchedViewport(benchmark::State& state) { | ||
Transform transform; | ||
transform.resize({ 512, 512 }); | ||
// slightly offset center so that tile order is better defined | ||
transform.setLatLng({ 0.1, -0.1 }); | ||
transform.setZoom(8); | ||
transform.setAngle(5.0); | ||
transform.setPitch(40.0 * M_PI / 180.0); | ||
|
||
std::size_t length = 0; | ||
while (state.KeepRunning()) { | ||
auto tiles = util::tileCover(transform.getState(), 8); | ||
length += tiles.size(); | ||
} | ||
} | ||
|
||
static void TileCoverBounds(benchmark::State& state) { | ||
std::size_t length = 0; | ||
while (state.KeepRunning()) { | ||
auto tiles = util::tileCover(sanFrancisco, 8); | ||
length += tiles.size(); | ||
} | ||
} | ||
|
||
static const auto geomPolygon = Polygon<double>{ | ||
{ | ||
{-122.5143814086914,37.779127216982424}, | ||
{-122.50811576843262,37.72721239056709}, | ||
{-122.50313758850099,37.70820178063929}, | ||
{-122.3938751220703,37.707454835665274}, | ||
{-122.37567901611328,37.70663997801684}, | ||
{-122.36297607421874,37.71343018466285}, | ||
{-122.354736328125,37.727280276860036}, | ||
{-122.36469268798828,37.73868429065797}, | ||
{-122.38014221191408,37.75442980295571}, | ||
{-122.38391876220702,37.78753873820529}, | ||
{-122.35919952392578,37.8065289741725}, | ||
{-122.35679626464844,37.820632846207864}, | ||
{-122.3712158203125,37.835276322922695}, | ||
{-122.3818588256836,37.82958198283902}, | ||
{-122.37190246582031,37.80788523279169}, | ||
{-122.38735198974608,37.791337175930686}, | ||
{-122.40966796874999,37.812767557570204}, | ||
{-122.46425628662108,37.807071480609274}, | ||
{-122.46803283691405,37.810326435534755}, | ||
{-122.47901916503906,37.81168262440736}, | ||
{-122.48966217041016,37.78916666399649}, | ||
{-122.50579833984375,37.78781006166096}, | ||
{-122.5143814086914,37.779127216982424} | ||
} | ||
}; | ||
|
||
static void TileCoverPolygon(benchmark::State& state) { | ||
std::size_t length = 0; | ||
|
||
while (state.KeepRunning()) { | ||
auto tiles = util::tileCover(geomPolygon, 8); | ||
length += tiles.size(); | ||
} | ||
} | ||
|
||
static void TileCountPolygon(benchmark::State& state) { | ||
std::size_t length = 0; | ||
|
||
while (state.KeepRunning()) { | ||
auto tiles = util::tileCount(geomPolygon, 16); | ||
length += tiles; | ||
} | ||
} | ||
|
||
BENCHMARK(TileCountBounds); | ||
BENCHMARK(TileCountPolygon); | ||
BENCHMARK(TileCoverPitchedViewport); | ||
BENCHMARK(TileCoverBounds); | ||
BENCHMARK(TileCoverPolygon); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,5 +23,6 @@ set(MBGL_BENCHMARK_FILES | |
|
||
# util | ||
benchmark/util/dtoa.benchmark.cpp | ||
benchmark/util/tilecover.benchmark.cpp | ||
|
||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,10 @@ | |
#include <mbgl/util/constants.hpp> | ||
#include <mbgl/util/interpolate.hpp> | ||
#include <mbgl/map/transform_state.hpp> | ||
#include <mbgl/util/tile_cover_impl.hpp> | ||
|
||
#include <functional> | ||
#include <list> | ||
|
||
namespace mbgl { | ||
|
||
|
@@ -27,10 +29,8 @@ struct edge { | |
} | ||
}; | ||
|
||
using ScanLine = const std::function<void(int32_t x0, int32_t x1, int32_t y)>; | ||
|
||
// scan-line conversion | ||
static void scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine scanLine) { | ||
static void scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, util::ScanLine scanLine) { | ||
double y0 = ::fmax(ymin, std::floor(e1.y0)); | ||
double y1 = ::fmin(ymax, std::ceil(e1.y1)); | ||
|
||
|
@@ -54,7 +54,7 @@ static void scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine sca | |
} | ||
|
||
// scan-line conversion | ||
static void scanTriangle(const Point<double>& a, const Point<double>& b, const Point<double>& c, int32_t ymin, int32_t ymax, ScanLine& scanLine) { | ||
static void scanTriangle(const Point<double>& a, const Point<double>& b, const Point<double>& c, int32_t ymin, int32_t ymax, util::ScanLine& scanLine) { | ||
edge ab = edge(a, b); | ||
edge bc = edge(b, c); | ||
edge ca = edge(c, a); | ||
|
@@ -147,11 +147,11 @@ std::vector<UnwrappedTileID> tileCover(const LatLngBounds& bounds_, int32_t z) { | |
{ std::min(bounds_.north(), util::LATITUDE_MAX), bounds_.east() }); | ||
|
||
return tileCover( | ||
TileCoordinate::fromLatLng(z, bounds.northwest()).p, | ||
TileCoordinate::fromLatLng(z, bounds.northeast()).p, | ||
TileCoordinate::fromLatLng(z, bounds.southeast()).p, | ||
TileCoordinate::fromLatLng(z, bounds.southwest()).p, | ||
TileCoordinate::fromLatLng(z, bounds.center()).p, | ||
Projection::project(bounds.northwest(), z), | ||
Projection::project(bounds.northeast(), z), | ||
Projection::project(bounds.southeast(), z), | ||
Projection::project(bounds.southwest(), z), | ||
Projection::project(bounds.center(), z), | ||
z); | ||
} | ||
|
||
|
@@ -169,25 +169,100 @@ std::vector<UnwrappedTileID> tileCover(const TransformState& state, int32_t z) { | |
z); | ||
} | ||
|
||
std::vector<UnwrappedTileID> tileCover(const Geometry<double>& geometry, int32_t z) { | ||
struct ID { | ||
int32_t x, y; | ||
}; | ||
|
||
std::list<ID> t; | ||
auto scanCover = [&](int32_t x0, int32_t x1, int32_t y) { | ||
for (auto x = x0; x < x1; x++) { | ||
t.emplace_back(ID{ x, y }); | ||
} | ||
}; | ||
|
||
TileCover tc(geometry, z, true); | ||
while (tc.next()) { | ||
tc.getTiles(scanCover); | ||
}; | ||
|
||
std::vector<UnwrappedTileID> result; | ||
result.reserve(t.size()); | ||
for (const auto& id : t) { | ||
result.emplace_back(z, id.x, id.y); | ||
} | ||
return result; | ||
} | ||
|
||
// Taken from https://github.com/mapbox/sphericalmercator#xyzbbox-zoom-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, 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 = ::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) - ::fmax(std::min(y1, y2), 0); | ||
|
||
return (maxX - minX + 1) * (maxY - minY + 1); | ||
uint64_t tileCount(const LatLngBounds& bounds, uint8_t zoom){ | ||
if (zoom == 0) { | ||
return 1; | ||
} | ||
auto sw = Projection::project(bounds.southwest(), zoom); | ||
auto ne = Projection::project(bounds.northeast(), zoom); | ||
auto maxTile = std::pow(2.0, zoom); | ||
auto x1 = floor(sw.x); | ||
auto x2 = ceil(ne.x) - 1; | ||
auto y1 = util::clamp(floor(sw.y), 0.0, maxTile - 1); | ||
auto y2 = util::clamp(floor(ne.y), 0.0, maxTile - 1); | ||
|
||
auto dx = x1 > x2 ? (maxTile - x1) + x2 : x2 - x1; | ||
auto dy = y1 - y2; | ||
return (dx + 1) * (dy + 1); | ||
} | ||
|
||
uint64_t tileCount(const Geometry<double>& geometry, uint8_t z) { | ||
uint64_t tileCount; | ||
auto scanCover = [&](int32_t x0, int32_t x1, int32_t) { | ||
for (auto x = x0; x < x1; x++) { | ||
tileCount+= (x1 - x0); | ||
} | ||
}; | ||
|
||
TileCover tc(geometry, z, true); | ||
while (tc.next()) { | ||
tc.getTiles(scanCover); | ||
}; | ||
return tileCount; | ||
} | ||
|
||
TileCover::TileCover(const LatLngBounds&bounds_, int32_t z) { | ||
LatLngBounds bounds = LatLngBounds::hull( | ||
{ std::max(bounds_.south(), -util::LATITUDE_MAX), bounds_.west() }, | ||
{ std::min(bounds_.north(), util::LATITUDE_MAX), bounds_.east() }); | ||
|
||
if (bounds.isEmpty() || | ||
bounds.south() > util::LATITUDE_MAX || | ||
bounds.north() < -util::LATITUDE_MAX) { | ||
bounds = LatLngBounds::world(); | ||
} | ||
|
||
auto sw = Projection::project(bounds.southwest(), z); | ||
auto ne = Projection::project(bounds.northeast(), z); | ||
auto se = Projection::project(bounds.southeast(), z); | ||
auto nw = Projection::project(bounds.northwest(), z); | ||
|
||
Polygon<double> p({ {sw, nw, ne, se, sw} }); | ||
impl = new TileCoverImpl(z, p, false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we want to use a smart pointer here instead? |
||
} | ||
|
||
TileCover::TileCover(const Geometry<double>& geom, int32_t z, bool project/* = true*/) { | ||
impl = new TileCoverImpl(z, geom, project); | ||
} | ||
|
||
TileCover::~TileCover() { | ||
delete impl; | ||
} | ||
|
||
bool TileCover::next() { | ||
return impl->next(); | ||
} | ||
|
||
bool TileCover::getTiles(ScanLine& scanCover) { | ||
return impl->scanRow(scanCover); | ||
} | ||
|
||
} // namespace util | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
#include <mbgl/tile/tile_id.hpp> | ||
#include <mbgl/style/types.hpp> | ||
#include <mbgl/util/tile_coordinate.hpp> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This header is not used here atm right? (only non-public header included here) |
||
#include <mbgl/util/geometry.hpp> | ||
|
||
#include <vector> | ||
|
||
|
@@ -13,13 +14,36 @@ class LatLngBounds; | |
|
||
namespace util { | ||
|
||
class TileCoverImpl; | ||
using ScanLine = const std::function<void(int32_t x0, int32_t x1, int32_t y)>; | ||
|
||
// Helper class to stream tile-cover results per row | ||
class TileCover { | ||
public: | ||
TileCover(const LatLngBounds&, int32_t z); | ||
// When project == true, projects the geometry points to tile coordinates | ||
TileCover(const Geometry<double>&, int32_t z, bool project = true); | ||
~TileCover(); | ||
|
||
//Returns false when there are no more rows to cover | ||
bool next(); | ||
//Invokes the ScanLine callback to indicate tiles coverd for the row from [x0,x1) | ||
// ScanLine may be invoked with duplcaite or overlapping ranges. | ||
bool getTiles(ScanLine&); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this API should work a bit differently, more like a generator, or C++ iterator rather than a function that accepts a callback so that the caller can drive iteration, rather than needing to process an entire scanline at once. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kkaefer I considered implementing a Per our chat, the streaming should be per-tile, and not per-row. |
||
|
||
private: | ||
TileCoverImpl* impl; | ||
}; | ||
|
||
int32_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); | ||
|
||
// Compute only the count of tiles needed for tileCover | ||
uint64_t tileCount(const LatLngBounds&, uint8_t z, uint16_t tileSize); | ||
uint64_t tileCount(const LatLngBounds&, uint8_t z); | ||
uint64_t tileCount(const Geometry<double>&, uint8_t z); | ||
|
||
} // namespace util | ||
} // namespace mbgl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method projects to a tile fraction and not to a tile-size based world. I'm not sure if it should be called
project
or exposed fromTileCoordinate