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

[core] Implement TileCover for polygonal regions #11267

Merged
merged 11 commits into from
Apr 26, 2018
96 changes: 96 additions & 0 deletions benchmark/util/tilecover.benchmark.cpp
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);

1 change: 1 addition & 0 deletions cmake/benchmark-files.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ set(MBGL_BENCHMARK_FILES

# util
benchmark/util/dtoa.benchmark.cpp
benchmark/util/tilecover.benchmark.cpp

)
2 changes: 2 additions & 0 deletions cmake/core-files.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,8 @@ set(MBGL_CORE_FILES
src/mbgl/util/tile_coordinate.hpp
src/mbgl/util/tile_cover.cpp
src/mbgl/util/tile_cover.hpp
src/mbgl/util/tile_cover_impl.cpp
src/mbgl/util/tile_cover_impl.hpp
src/mbgl/util/tile_range.hpp
src/mbgl/util/tiny_sdf.cpp
src/mbgl/util/tiny_sdf.hpp
Expand Down
16 changes: 4 additions & 12 deletions include/mbgl/util/projection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ class Projection {
return project_(latLng, worldSize(scale));
}

static Point<double> project(const LatLng& latLng, uint8_t zoom) {
return project_(latLng, std::pow(2.0, zoom));
//Returns point on tile
static Point<double> project(const LatLng& latLng, int32_t zoom) {
Copy link
Contributor Author

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 from TileCoordinate

return project_(latLng, 1 << zoom);
}

static LatLng unproject(const Point<double>& p, double scale, LatLng::WrapMode wrapMode = LatLng::Unwrapped) {
Expand All @@ -90,16 +91,7 @@ class Projection {
wrapMode
};
}

// Project lat, lon to point in a zoom-dependent world size
static Point<double> project(const LatLng& point, uint8_t zoom, uint16_t tileSize) {
const double t2z = tileSize * std::pow(2, zoom);
Point<double> pt = project_(point, t2z);
// Flip y coordinate
auto x = ::round(std::min(pt.x, t2z));
auto y = ::round(std::min(t2z - pt.y, t2z));
return { x, y };
}

private:
static Point<double> project_(const LatLng& latLng, double worldSize) {
return Point<double> {
Expand Down
2 changes: 1 addition & 1 deletion platform/default/mbgl/storage/offline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ uint64_t OfflineTilePyramidRegionDefinition::tileCount(style::SourceType type, u
const Range<uint8_t> 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);
result += util::tileCount(bounds, z);
}

return result;
Expand Down
125 changes: 100 additions & 25 deletions src/mbgl/util/tile_cover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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));

Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
Copy link
Contributor

@ivovandongen ivovandongen Apr 10, 2018

Choose a reason for hiding this comment

The 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
Expand Down
26 changes: 25 additions & 1 deletion src/mbgl/util/tile_cover.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <mbgl/tile/tile_id.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/util/tile_coordinate.hpp>
Copy link
Contributor

Choose a reason for hiding this comment

The 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>

Expand All @@ -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&);
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kkaefer I considered implementing a ForwardIterator, but found that std::iterators are required to be CopyConstructible and CopyAssignable. Copying the TileCover::Impl state is non-trivial and would affect performance if the iterator was used incorrectly.

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
Loading