summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsheem Mamoowala <asheem.mamoowala@mapbox.com>2018-01-05 06:35:31 -0800
committerGitHub <noreply@github.com>2018-01-05 06:35:31 -0800
commit10a44050f485a18f8dd6523aca6a7a9f82f7afc7 (patch)
treefea7f30e35b3228b5989250ff9db225e12bd10ed
parentbfa4cea24c2ab3973f845fda6da6d4a9e8f03e56 (diff)
downloadqtlocation-mapboxgl-10a44050f485a18f8dd6523aca6a7a9f82f7afc7.tar.gz
Support TileJSON bounds property (#10701)
* [core] Parse TileJSON bounds property * [core] Add TileRange and LatLngBounds::contains(CanonicalTileID) Move LatLngBounds::contains impl to cpp file * [core] Skip tile creation outside of tileset bounds * [core] Fix TileRange for wrapped bounds and use for CustomTileLoader instead of LatLngBounds comparisons for tiles.
-rw-r--r--cmake/core-files.cmake1
-rw-r--r--cmake/test-files.cmake4
-rw-r--r--include/mbgl/util/geo.hpp78
-rw-r--r--include/mbgl/util/projection.hpp4
-rw-r--r--include/mbgl/util/tileset.hpp13
-rw-r--r--platform/node/test/ignores.json1
-rw-r--r--src/mbgl/algorithm/update_renderables.hpp6
-rw-r--r--src/mbgl/annotation/render_annotation_source.cpp1
-rw-r--r--src/mbgl/renderer/sources/render_custom_geometry_source.cpp1
-rw-r--r--src/mbgl/renderer/sources/render_geojson_source.cpp1
-rw-r--r--src/mbgl/renderer/sources/render_raster_source.cpp1
-rw-r--r--src/mbgl/renderer/sources/render_vector_source.cpp1
-rw-r--r--src/mbgl/renderer/tile_pyramid.cpp11
-rw-r--r--src/mbgl/renderer/tile_pyramid.hpp1
-rw-r--r--src/mbgl/style/conversion/tileset.cpp31
-rw-r--r--src/mbgl/style/custom_tile_loader.cpp11
-rw-r--r--src/mbgl/util/geo.cpp82
-rw-r--r--src/mbgl/util/tile_range.hpp47
-rw-r--r--test/style/conversion/tileset.test.cpp72
-rw-r--r--test/util/tile_range.test.cpp56
20 files changed, 339 insertions, 84 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake
index 15812da42d..5c060f4698 100644
--- a/cmake/core-files.cmake
+++ b/cmake/core-files.cmake
@@ -690,6 +690,7 @@ 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_range.hpp
src/mbgl/util/tiny_sdf.cpp
src/mbgl/util/tiny_sdf.hpp
src/mbgl/util/token.hpp
diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake
index 55420ff2a8..381c609719 100644
--- a/cmake/test-files.cmake
+++ b/cmake/test-files.cmake
@@ -88,6 +88,7 @@ set(MBGL_TEST_FILES
test/style/conversion/layer.test.cpp
test/style/conversion/light.test.cpp
test/style/conversion/stringify.test.cpp
+ test/style/conversion/tileset.test.cpp
# style/expression
test/style/expression/expression.test.cpp
@@ -113,9 +114,9 @@ set(MBGL_TEST_FILES
# text
test/text/cross_tile_symbol_index.test.cpp
- test/text/local_glyph_rasterizer.test.cpp
test/text/glyph_manager.test.cpp
test/text/glyph_pbf.test.cpp
+ test/text/local_glyph_rasterizer.test.cpp
test/text/quads.test.cpp
# tile
@@ -146,6 +147,7 @@ set(MBGL_TEST_FILES
test/util/thread.test.cpp
test/util/thread_local.test.cpp
test/util/tile_cover.test.cpp
+ test/util/tile_range.test.cpp
test/util/timer.test.cpp
test/util/token.test.cpp
test/util/unique_any.test.cpp
diff --git a/include/mbgl/util/geo.hpp b/include/mbgl/util/geo.hpp
index 60043ee156..dacdb968f3 100644
--- a/include/mbgl/util/geo.hpp
+++ b/include/mbgl/util/geo.hpp
@@ -158,81 +158,11 @@ public:
return (sw.wrapped().longitude() > ne.wrapped().longitude());
}
- bool contains(const LatLng& point, LatLng::WrapMode wrap = LatLng::Unwrapped) const {
- bool containsLatitude = point.latitude() >= sw.latitude() &&
- point.latitude() <= ne.latitude();
- if (!containsLatitude) {
- return false;
- }
-
- bool containsUnwrappedLongitude = point.longitude() >= sw.longitude() &&
- point.longitude() <= ne.longitude();
- if (containsUnwrappedLongitude) {
- return true;
- } else if (wrap == LatLng::Wrapped) {
- LatLngBounds wrapped(sw.wrapped(), ne.wrapped());
- auto ptLon = point.wrapped().longitude();
- if (crossesAntimeridian()) {
- return (ptLon >= wrapped.sw.longitude() &&
- ptLon <= util::LONGITUDE_MAX) ||
- (ptLon <= wrapped.ne.longitude() &&
- ptLon >= -util::LONGITUDE_MAX);
- } else {
- return (ptLon >= wrapped.sw.longitude() &&
- ptLon <= wrapped.ne.longitude());
- }
- }
- return false;
- }
+ bool contains(const CanonicalTileID& tileID) const;
+ bool contains(const LatLng& point, LatLng::WrapMode wrap = LatLng::Unwrapped) const;
+ bool contains(const LatLngBounds& area, LatLng::WrapMode wrap = LatLng::Unwrapped) const;
- bool contains(const LatLngBounds& area, LatLng::WrapMode wrap = LatLng::Unwrapped) const {
- bool containsLatitude = area.north() <= north() && area.south() >= south();
- if (!containsLatitude) {
- return false;
- }
-
- bool containsUnwrapped = area.east() <= east() && area.west() >= west();
- if(containsUnwrapped) {
- return true;
- } else if (wrap == LatLng::Wrapped) {
- LatLngBounds wrapped(sw.wrapped(), ne.wrapped());
- LatLngBounds other(area.sw.wrapped(), area.ne.wrapped());
- if (crossesAntimeridian() & !area.crossesAntimeridian()) {
- return (other.east() <= util::LONGITUDE_MAX && other.west() >= wrapped.west()) ||
- (other.east() <= wrapped.east() && other.west() >= -util::LONGITUDE_MAX);
- } else {
- return other.east() <= wrapped.east() && other.west() >= wrapped.west();
- }
- }
- return false;
- }
-
- bool intersects(const LatLngBounds area, LatLng::WrapMode wrap = LatLng::Unwrapped) const {
- bool latitudeIntersects = area.north() > south() && area.south() < north();
- if (!latitudeIntersects) {
- return false;
- }
-
- bool longitudeIntersects = area.east() > west() && area.west() < east();
- if (longitudeIntersects) {
- return true;
- } else if (wrap == LatLng::Wrapped) {
- LatLngBounds wrapped(sw.wrapped(), ne.wrapped());
- LatLngBounds other(area.sw.wrapped(), area.ne.wrapped());
- if (crossesAntimeridian()) {
- return area.crossesAntimeridian() ||
- other.east() > wrapped.west() ||
- other.west() < wrapped.east();
- } else if (other.crossesAntimeridian()){
- return other.east() > wrapped.west() ||
- other.west() < wrapped.east();
- } else {
- return other.east() > wrapped.west() &&
- other.west() < wrapped.east();
- }
- }
- return false;
- }
+ bool intersects(const LatLngBounds area, LatLng::WrapMode wrap = LatLng::Unwrapped) const;
private:
LatLng sw;
diff --git a/include/mbgl/util/projection.hpp b/include/mbgl/util/projection.hpp
index 1613af3b36..b4a34521a4 100644
--- a/include/mbgl/util/projection.hpp
+++ b/include/mbgl/util/projection.hpp
@@ -78,6 +78,10 @@ public:
return project_(latLng, worldSize(scale));
}
+ static Point<double> project(const LatLng& latLng, uint8_t zoom) {
+ return project_(latLng, std::pow(2.0, zoom));
+ }
+
static LatLng unproject(const Point<double>& p, double scale, LatLng::WrapMode wrapMode = LatLng::Unwrapped) {
auto p2 = p * util::DEGREES_MAX / worldSize(scale);
return LatLng {
diff --git a/include/mbgl/util/tileset.hpp b/include/mbgl/util/tileset.hpp
index 5a03e1a9da..7bef0e89ed 100644
--- a/include/mbgl/util/tileset.hpp
+++ b/include/mbgl/util/tileset.hpp
@@ -2,7 +2,8 @@
#include <mbgl/util/range.hpp>
#include <mbgl/util/constants.hpp>
-
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/geo.hpp>
#include <tuple>
#include <vector>
#include <string>
@@ -18,6 +19,7 @@ public:
Range<uint8_t> zoomRange;
std::string attribution;
Scheme scheme;
+ optional<LatLngBounds> bounds;
Tileset(std::vector<std::string> tiles_ = std::vector<std::string>(),
Range<uint8_t> zoomRange_ = { 0, util::DEFAULT_MAX_ZOOM },
@@ -26,13 +28,14 @@ public:
: tiles(std::move(tiles_)),
zoomRange(std::move(zoomRange_)),
attribution(std::move(attribution_)),
- scheme(scheme_) {}
+ scheme(scheme_),
+ bounds() {}
- // TileJSON also includes center, zoom, and bounds, but they are not used by mbgl.
+ // TileJSON also includes center and zoom but they are not used by mbgl.
friend bool operator==(const Tileset& lhs, const Tileset& rhs) {
- return std::tie(lhs.tiles, lhs.zoomRange, lhs.attribution, lhs.scheme)
- == std::tie(rhs.tiles, rhs.zoomRange, rhs.attribution, rhs.scheme);
+ return std::tie(lhs.tiles, lhs.zoomRange, lhs.attribution, lhs.scheme, lhs.bounds)
+ == std::tie(rhs.tiles, rhs.zoomRange, rhs.attribution, rhs.scheme, rhs.bounds);
}
};
diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json
index 10e5e88e79..62e042cd4d 100644
--- a/platform/node/test/ignores.json
+++ b/platform/node/test/ignores.json
@@ -85,7 +85,6 @@
"render-tests/text-pitch-alignment/map-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-alignment/viewport-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/text-pitch-scaling/line-half": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
- "render-tests/tilejson-bounds/default": "https://github.com/mapbox/mapbox-gl-native/pull/10701",
"render-tests/video/default": "skip - https://github.com/mapbox/mapbox-gl-native/issues/601",
"render-tests/background-color/colorSpace-hcl": "needs issue",
"render-tests/hillshade-accent-color/default": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
diff --git a/src/mbgl/algorithm/update_renderables.hpp b/src/mbgl/algorithm/update_renderables.hpp
index c583b6b2b6..5fbe0d943f 100644
--- a/src/mbgl/algorithm/update_renderables.hpp
+++ b/src/mbgl/algorithm/update_renderables.hpp
@@ -35,7 +35,11 @@ void updateRenderables(GetTileFn getTile,
auto tile = getTile(idealDataTileID);
if (!tile) {
tile = createTile(idealDataTileID);
- assert(tile);
+ // For source types where TileJSON.bounds is set, tiles outside the
+ // bounds are not created
+ if(tile == nullptr) {
+ continue;
+ }
}
// if (source has the tile and bucket is loaded) {
diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp
index a0b69af8d5..38ca5ccd0b 100644
--- a/src/mbgl/annotation/render_annotation_source.cpp
+++ b/src/mbgl/annotation/render_annotation_source.cpp
@@ -41,6 +41,7 @@ void RenderAnnotationSource::update(Immutable<style::Source::Impl> baseImpl_,
// Zoom level 16 is typically sufficient for annotations.
// See https://github.com/mapbox/mapbox-gl-native/issues/10197
{ 0, 16 },
+ optional<LatLngBounds> {},
[&] (const OverscaledTileID& tileID) {
return std::make_unique<AnnotationTile>(tileID, parameters);
});
diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp
index 111f0234ed..df615a7e20 100644
--- a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp
+++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp
@@ -44,6 +44,7 @@ void RenderCustomGeometrySource::update(Immutable<style::Source::Impl> baseImpl_
SourceType::CustomVector,
util::tileSize,
impl().getZoomRange(),
+ {},
[&] (const OverscaledTileID& tileID) {
return std::make_unique<CustomGeometryTile>(tileID, impl().id, parameters, impl().getTileOptions(), *tileLoader);
});
diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp
index d07cfcdc41..8ea80cd813 100644
--- a/src/mbgl/renderer/sources/render_geojson_source.cpp
+++ b/src/mbgl/renderer/sources/render_geojson_source.cpp
@@ -62,6 +62,7 @@ void RenderGeoJSONSource::update(Immutable<style::Source::Impl> baseImpl_,
SourceType::GeoJSON,
util::tileSize,
impl().getZoomRange(),
+ optional<LatLngBounds>{},
[&] (const OverscaledTileID& tileID) {
return std::make_unique<GeoJSONTile>(tileID, impl().id, parameters, data->getTile(tileID.canonical));
});
diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp
index f11f9b7aed..e99cd040e9 100644
--- a/src/mbgl/renderer/sources/render_raster_source.cpp
+++ b/src/mbgl/renderer/sources/render_raster_source.cpp
@@ -52,6 +52,7 @@ void RenderRasterSource::update(Immutable<style::Source::Impl> baseImpl_,
SourceType::Raster,
impl().getTileSize(),
tileset->zoomRange,
+ tileset->bounds,
[&] (const OverscaledTileID& tileID) {
return std::make_unique<RasterTile>(tileID, parameters, *tileset);
});
diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp
index 49f8fdff2c..d53023e4d0 100644
--- a/src/mbgl/renderer/sources/render_vector_source.cpp
+++ b/src/mbgl/renderer/sources/render_vector_source.cpp
@@ -55,6 +55,7 @@ void RenderVectorSource::update(Immutable<style::Source::Impl> baseImpl_,
SourceType::Vector,
util::tileSize,
tileset->zoomRange,
+ tileset->bounds,
[&] (const OverscaledTileID& tileID) {
return std::make_unique<VectorTile>(tileID, impl().id, parameters, *tileset);
});
diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp
index 6a711fb6d5..e474737f8d 100644
--- a/src/mbgl/renderer/tile_pyramid.cpp
+++ b/src/mbgl/renderer/tile_pyramid.cpp
@@ -7,6 +7,7 @@
#include <mbgl/map/transform.hpp>
#include <mbgl/math/clamp.hpp>
#include <mbgl/util/tile_cover.hpp>
+#include <mbgl/util/tile_range.hpp>
#include <mbgl/util/enum.hpp>
#include <mbgl/util/logging.hpp>
@@ -14,6 +15,7 @@
#include <mapbox/geometry/envelope.hpp>
+#include <cmath>
#include <algorithm>
namespace mbgl {
@@ -61,6 +63,7 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
const SourceType type,
const uint16_t tileSize,
const Range<uint8_t> zoomRange,
+ optional<LatLngBounds> bounds,
std::function<std::unique_ptr<Tile> (const OverscaledTileID&)> createTile) {
// If we need a relayout, abandon any cached tiles; they're now stale.
if (needsRelayout) {
@@ -135,7 +138,15 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
auto it = tiles.find(tileID);
return it == tiles.end() ? nullptr : it->second.get();
};
+
+ optional<util::TileRange> tileRange = {};
+ if (bounds) {
+ tileRange = util::TileRange::fromLatLngBounds(*bounds, std::min(tileZoom, (int32_t)zoomRange.max));
+ }
auto createTileFn = [&](const OverscaledTileID& tileID) -> Tile* {
+ if (tileRange && !tileRange->contains(tileID.canonical)) {
+ return nullptr;
+ }
std::unique_ptr<Tile> tile = cache.get(tileID);
if (!tile) {
tile = createTile(tileID);
diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp
index feab8a838c..3755cee06d 100644
--- a/src/mbgl/renderer/tile_pyramid.hpp
+++ b/src/mbgl/renderer/tile_pyramid.hpp
@@ -40,6 +40,7 @@ public:
style::SourceType type,
uint16_t tileSize,
Range<uint8_t> zoomRange,
+ optional<LatLngBounds> bounds,
std::function<std::unique_ptr<Tile> (const OverscaledTileID&)> createTile);
void startRender(PaintParameters&);
diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp
index b9383c41b8..6e559c0cac 100644
--- a/src/mbgl/style/conversion/tileset.cpp
+++ b/src/mbgl/style/conversion/tileset.cpp
@@ -1,9 +1,14 @@
#include <mbgl/style/conversion/tileset.hpp>
+#include <mbgl/util/geo.hpp>
namespace mbgl {
namespace style {
namespace conversion {
+bool validateLatitude(const double lat) {
+ return lat < 90 && lat > -90;
+}
+
optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error& error) const {
Tileset result;
@@ -65,6 +70,32 @@ optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error
result.attribution = std::move(*attribution);
}
+ auto boundsValue = objectMember(value, "bounds");
+ if (boundsValue) {
+ if (!isArray(*boundsValue) || arrayLength(*boundsValue) != 4) {
+ error = { "bounds must be an array with left, bottom, top, and right values" };
+ return {};
+ }
+ optional<double> left = toDouble(arrayMember(*boundsValue, 0));
+ optional<double> bottom = toDouble(arrayMember(*boundsValue, 1));
+ optional<double> right = toDouble(arrayMember(*boundsValue, 2));
+ optional<double> top = toDouble(arrayMember(*boundsValue, 3));
+
+ if (!left || !right || !bottom || !top) {
+ error = { "bounds array must contain numeric longitude and latitude values" };
+ return {};
+ }
+ if (!validateLatitude(*bottom) || !validateLatitude(*top) || top <= bottom){
+ error = { "bounds latitude values must be between -90 and 90 with bottom less than top" };
+ return {};
+ }
+ if(*left >= *right) {
+ error = { "bounds left longitude should be less than right longitude" };
+ return {};
+ }
+ result.bounds = LatLngBounds::hull({ *bottom, *left }, { *top, *right });
+ }
+
return result;
}
diff --git a/src/mbgl/style/custom_tile_loader.cpp b/src/mbgl/style/custom_tile_loader.cpp
index 76248b84bd..1c587302b8 100644
--- a/src/mbgl/style/custom_tile_loader.cpp
+++ b/src/mbgl/style/custom_tile_loader.cpp
@@ -1,5 +1,6 @@
#include <mbgl/style/custom_tile_loader.hpp>
#include <mbgl/tile/custom_geometry_tile.hpp>
+#include <mbgl/util/tile_range.hpp>
namespace mbgl {
namespace style {
@@ -79,9 +80,15 @@ void CustomTileLoader::invalidateTile(const CanonicalTileID& tileID) {
}
void CustomTileLoader::invalidateRegion(const LatLngBounds& bounds, Range<uint8_t> ) {
+ std::map<uint8_t, util::TileRange> tileRanges;
+
for (auto idtuple= tileCallbackMap.begin(); idtuple != tileCallbackMap.end(); idtuple++) {
- const LatLngBounds tileBounds(idtuple->first);
- if (tileBounds.intersects(bounds, LatLng::Wrapped) || bounds.contains(tileBounds, LatLng::Wrapped) || tileBounds.contains(bounds, LatLng::Wrapped)) {
+ auto zoom = idtuple->first.z;
+ auto tileRange = tileRanges.find(zoom);
+ if(tileRange == tileRanges.end()) {
+ tileRange = tileRanges.emplace(std::make_pair(zoom, util::TileRange::fromLatLngBounds(bounds, zoom))).first;
+ }
+ if (tileRange->second.contains(idtuple->first)) {
for (auto iter = idtuple->second.begin(); iter != idtuple->second.end(); iter++) {
auto actor = std::get<2>(*iter);
actor.invoke(&CustomGeometryTile::invalidateTileData);
diff --git a/src/mbgl/util/geo.cpp b/src/mbgl/util/geo.cpp
index f38aba20c4..a04e2c5355 100644
--- a/src/mbgl/util/geo.cpp
+++ b/src/mbgl/util/geo.cpp
@@ -1,6 +1,8 @@
#include <mbgl/util/geo.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/tile/tile_id.hpp>
+#include <mbgl/math/clamp.hpp>
+#include <mbgl/util/tile_range.hpp>
#include <cmath>
@@ -32,6 +34,86 @@ LatLngBounds::LatLngBounds(const CanonicalTileID& id)
ne({ lat_(id.z, id.y), lon_(id.z, id.x + 1) }) {
}
+bool LatLngBounds::contains(const CanonicalTileID& tileID) const {
+ return util::TileRange::fromLatLngBounds(*this, tileID.z).contains(tileID);
+}
+
+bool LatLngBounds::contains(const LatLng& point, LatLng::WrapMode wrap /*= LatLng::Unwrapped*/) const {
+ bool containsLatitude = point.latitude() >= sw.latitude() &&
+ point.latitude() <= ne.latitude();
+ if (!containsLatitude) {
+ return false;
+ }
+
+ bool containsUnwrappedLongitude = point.longitude() >= sw.longitude() &&
+ point.longitude() <= ne.longitude();
+ if (containsUnwrappedLongitude) {
+ return true;
+ } else if (wrap == LatLng::Wrapped) {
+ LatLngBounds wrapped(sw.wrapped(), ne.wrapped());
+ auto ptLon = point.wrapped().longitude();
+ if (crossesAntimeridian()) {
+ return (ptLon >= wrapped.sw.longitude() &&
+ ptLon <= util::LONGITUDE_MAX) ||
+ (ptLon <= wrapped.ne.longitude() &&
+ ptLon >= -util::LONGITUDE_MAX);
+ } else {
+ return (ptLon >= wrapped.sw.longitude() &&
+ ptLon <= wrapped.ne.longitude());
+ }
+ }
+ return false;
+}
+
+bool LatLngBounds::contains(const LatLngBounds& area, LatLng::WrapMode wrap /*= LatLng::Unwrapped*/) const {
+ bool containsLatitude = area.north() <= north() && area.south() >= south();
+ if (!containsLatitude) {
+ return false;
+ }
+
+ bool containsUnwrapped = area.east() <= east() && area.west() >= west();
+ if(containsUnwrapped) {
+ return true;
+ } else if (wrap == LatLng::Wrapped) {
+ LatLngBounds wrapped(sw.wrapped(), ne.wrapped());
+ LatLngBounds other(area.sw.wrapped(), area.ne.wrapped());
+ if (crossesAntimeridian() & !area.crossesAntimeridian()) {
+ return (other.east() <= util::LONGITUDE_MAX && other.west() >= wrapped.west()) ||
+ (other.east() <= wrapped.east() && other.west() >= -util::LONGITUDE_MAX);
+ } else {
+ return other.east() <= wrapped.east() && other.west() >= wrapped.west();
+ }
+ }
+ return false;
+}
+
+bool LatLngBounds::intersects(const LatLngBounds area, LatLng::WrapMode wrap /*= LatLng::Unwrapped*/) const {
+ bool latitudeIntersects = area.north() > south() && area.south() < north();
+ if (!latitudeIntersects) {
+ return false;
+ }
+
+ bool longitudeIntersects = area.east() > west() && area.west() < east();
+ if (longitudeIntersects) {
+ return true;
+ } else if (wrap == LatLng::Wrapped) {
+ LatLngBounds wrapped(sw.wrapped(), ne.wrapped());
+ LatLngBounds other(area.sw.wrapped(), area.ne.wrapped());
+ if (crossesAntimeridian()) {
+ return area.crossesAntimeridian() ||
+ other.east() > wrapped.west() ||
+ other.west() < wrapped.east();
+ } else if (other.crossesAntimeridian()){
+ return other.east() > wrapped.west() ||
+ other.west() < wrapped.east();
+ } else {
+ return other.east() > wrapped.west() &&
+ other.west() < wrapped.east();
+ }
+ }
+ return false;
+}
+
ScreenCoordinate EdgeInsets::getCenter(uint16_t width, uint16_t height) const {
return {
(width - left() - right()) / 2.0 + left(),
diff --git a/src/mbgl/util/tile_range.hpp b/src/mbgl/util/tile_range.hpp
new file mode 100644
index 0000000000..f630a49078
--- /dev/null
+++ b/src/mbgl/util/tile_range.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <mbgl/tile/tile_id.hpp>
+#include <mbgl/util/range.hpp>
+#include <mbgl/util/geo.hpp>
+#include <mbgl/util/projection.hpp>
+
+namespace mbgl {
+
+namespace util {
+
+class TileRange {
+public:
+ Range<Point<double>> range;
+ uint8_t z;
+
+ // Compute the range of tiles covered by the bounds.
+ static TileRange fromLatLngBounds(const LatLngBounds& bounds, uint8_t z) {
+ auto swProj = Projection::project(bounds.southwest().wrapped(), z);
+ auto ne = bounds.northeast();
+ auto neProj = Projection::project(ne.longitude() > util::LONGITUDE_MAX ? ne.wrapped() : ne , z);
+ const auto minX = std::floor(swProj.x);
+ const auto maxX = std::ceil(neProj.x);
+ const auto minY = std::floor(neProj.y);
+ const auto maxY = std::ceil(swProj.y);
+ return TileRange({ {minX, minY}, {maxX, maxY} }, z);
+ }
+
+ bool contains(const CanonicalTileID& tileID) {
+ return z == tileID.z &&
+ (range.min.x >= range.max.x ? //For wrapped bounds
+ tileID.x >= range.min.x || tileID.x < range.max.x :
+ tileID.x < range.max.x && tileID.x >= range.min.x) &&
+ tileID.y < range.max.y &&
+ tileID.y >= range.min.y;
+ }
+
+private:
+ TileRange(Range<Point<double>> range_, uint8_t z_)
+ : range(range_),
+ z(z_) {
+ }
+
+};
+
+} // namespace util
+} // namespace mbgl
diff --git a/test/style/conversion/tileset.test.cpp b/test/style/conversion/tileset.test.cpp
new file mode 100644
index 0000000000..8002cd038f
--- /dev/null
+++ b/test/style/conversion/tileset.test.cpp
@@ -0,0 +1,72 @@
+#include <mbgl/test/util.hpp>
+
+#include <mbgl/style/conversion/json.hpp>
+#include <mbgl/style/conversion/tileset.hpp>
+
+#include <mbgl/util/logging.hpp>
+
+using namespace mbgl;
+using namespace mbgl::style::conversion;
+
+TEST(Tileset, Empty) {
+ Error error;
+ mbgl::optional<Tileset> converted = convertJSON<Tileset>("{}", error);
+ EXPECT_FALSE((bool) converted);
+}
+
+TEST(Tileset, ErrorHandling) {
+ Error error;
+ mbgl::optional<Tileset> converted = convertJSON<Tileset>(R"JSON({
+ "tiles": "should not be a string"
+ })JSON", error);
+ EXPECT_FALSE((bool) converted);
+}
+
+TEST(Tileset, InvalidBounds) {
+ {
+ Error error;
+ mbgl::optional<Tileset> converted = convertJSON<Tileset>(R"JSON({
+ "tiles": ["http://mytiles"],
+ "bounds": [73, -180, -73, -120]
+ })JSON", error);
+
+ EXPECT_FALSE((bool) converted);
+ }
+ {
+ Error error;
+ mbgl::optional<Tileset> converted = convertJSON<Tileset>(R"JSON({
+ "tiles": ["http://mytiles"],
+ "bounds": [-120]
+ })JSON", error);
+
+ EXPECT_FALSE((bool) converted);
+ }
+ {
+ Error error;
+ mbgl::optional<Tileset> converted = convertJSON<Tileset>(R"JSON({
+ "tiles": ["http://mytiles"],
+ "bounds": "should not be a string"
+ })JSON", error);
+
+ EXPECT_FALSE((bool) converted);
+ }
+}
+
+TEST(Tileset, FullConversion) {
+ Error error;
+ Tileset converted = *convertJSON<Tileset>(R"JSON({
+ "tiles": ["http://mytiles"],
+ "scheme": "xyz",
+ "minzoom": 1,
+ "maxzoom": 2,
+ "attribution": "mapbox",
+ "bounds": [-180, -73, -120, 73]
+ })JSON", error);
+
+ EXPECT_EQ(converted.tiles[0], "http://mytiles");
+ EXPECT_EQ(converted.scheme, Tileset::Scheme::XYZ);
+ EXPECT_EQ(converted.zoomRange.min, 1);
+ EXPECT_EQ(converted.zoomRange.max, 2);
+ EXPECT_EQ(converted.attribution, "mapbox");
+ EXPECT_EQ(converted.bounds, LatLngBounds::hull({73, -180}, {-73, -120}));
+}
diff --git a/test/util/tile_range.test.cpp b/test/util/tile_range.test.cpp
new file mode 100644
index 0000000000..dc8ae28705
--- /dev/null
+++ b/test/util/tile_range.test.cpp
@@ -0,0 +1,56 @@
+
+#include <mbgl/util/tile_range.hpp>
+#include <mbgl/util/geo.hpp>
+#include <mbgl/map/transform.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace mbgl;
+
+TEST(TileRange, ContainsWorld) {
+ auto range = util::TileRange::fromLatLngBounds(LatLngBounds::world(), 0);
+ EXPECT_TRUE(range.contains(CanonicalTileID(0, 0, 0)));
+ EXPECT_FALSE(range.contains(CanonicalTileID(10, 0, 0)));
+}
+
+TEST(TileRange, ContainsBoundsFromTile) {
+ {
+ const LatLngBounds bounds{ CanonicalTileID(3, 7, 0) };
+ auto range = util::TileRange::fromLatLngBounds(bounds, 3);
+ EXPECT_TRUE(range.contains(CanonicalTileID(3, 7, 0)));
+ }
+ {
+ const LatLngBounds bounds{ CanonicalTileID(10, 162, 395) };
+ auto range = util::TileRange::fromLatLngBounds(bounds, 10);
+ EXPECT_TRUE(range.contains(CanonicalTileID(10, 162, 395)));
+ }
+}
+TEST(TileRange, ContainsIntersectingTiles) {
+ auto bounds = LatLngBounds::hull({ 37.6609, -122.5744 }, { 37.8271, -122.3204 });
+ auto range = util::TileRange::fromLatLngBounds(bounds, 13);
+ EXPECT_FALSE(range.contains(CanonicalTileID(13, 1316, 3100)));
+ EXPECT_TRUE(range.contains(CanonicalTileID(13, 1310, 3166)));
+}
+
+TEST(TileRange, ContainsWrappedBounds) {
+ auto wrappedBounds = LatLngBounds::hull({ 37.6609, 237.6796 }, { 37.8271, 237.4256 });
+ auto range = util::TileRange::fromLatLngBounds(wrappedBounds, 13);
+ EXPECT_FALSE(range.contains(CanonicalTileID(13, 1316, 3100)));
+ EXPECT_TRUE(range.contains(CanonicalTileID(13, 1310, 3166)));
+}
+
+TEST(TileRange, ContainsBoundsCrossingAntimeridian) {
+ {
+ auto cossingBounds = LatLngBounds::hull({-20.9615, -214.309}, {19.477, -155.830});
+ auto range = util::TileRange::fromLatLngBounds(cossingBounds, 1);
+ EXPECT_TRUE(range.contains(CanonicalTileID(1, 1, 1)));
+ EXPECT_TRUE(range.contains(CanonicalTileID(1, 0, 0)));
+ }
+ {
+ auto cossingBounds = LatLngBounds::hull({-20.9615, -214.309}, {19.477, -155.830});
+ auto range = util::TileRange::fromLatLngBounds(cossingBounds, 6);
+ EXPECT_FALSE(range.contains(CanonicalTileID(6, 55, 34)));
+ EXPECT_FALSE(range.contains(CanonicalTileID(6, 5, 28)));
+ EXPECT_TRUE(range.contains(CanonicalTileID(6, 63, 28)));
+ }
+}