summaryrefslogtreecommitdiff
path: root/src/mbgl/map
diff options
context:
space:
mode:
authorLeith Bade <leith@mapbox.com>2014-12-05 23:16:49 +1100
committerLeith Bade <leith@mapbox.com>2014-12-05 23:16:49 +1100
commitde9eb00276684a10f49a1c490f55266b80238155 (patch)
treefc713b06541bb1af04e95c70f2e383bcb86a164a /src/mbgl/map
parentc348c141c5c5754c962d9b7e94af83f097e30487 (diff)
parentff640132de0fe855314a8fd86adae3a2fb33237b (diff)
downloadqtlocation-mapboxgl-de9eb00276684a10f49a1c490f55266b80238155.tar.gz
Merge branch 'master' of github.com:mapbox/mapbox-gl-native into android-mason
Conflicts: .gitignore gyp/mbgl-ios.gypi gyp/mbgl-linux.gypi gyp/mbgl-osx.gypi include/mbgl/map/map.hpp src/mbgl/map/map.cpp src/mbgl/storage/caching_http_file_source.cpp
Diffstat (limited to 'src/mbgl/map')
-rw-r--r--src/mbgl/map/map.cpp745
-rw-r--r--src/mbgl/map/raster_tile_data.cpp34
-rw-r--r--src/mbgl/map/raster_tile_data.hpp33
-rw-r--r--src/mbgl/map/source.cpp369
-rw-r--r--src/mbgl/map/source.hpp86
-rw-r--r--src/mbgl/map/sprite.cpp152
-rw-r--r--src/mbgl/map/sprite.hpp79
-rw-r--r--src/mbgl/map/tile.cpp147
-rw-r--r--src/mbgl/map/tile_data.cpp104
-rw-r--r--src/mbgl/map/tile_data.hpp88
-rw-r--r--src/mbgl/map/tile_parser.cpp174
-rw-r--r--src/mbgl/map/tile_parser.hpp77
-rw-r--r--src/mbgl/map/transform.cpp472
-rw-r--r--src/mbgl/map/transform_state.cpp172
-rw-r--r--src/mbgl/map/vector_tile.cpp214
-rw-r--r--src/mbgl/map/vector_tile.hpp118
-rw-r--r--src/mbgl/map/vector_tile_data.cpp78
-rw-r--r--src/mbgl/map/vector_tile_data.hpp74
18 files changed, 3216 insertions, 0 deletions
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
new file mode 100644
index 0000000000..b0a06e8711
--- /dev/null
+++ b/src/mbgl/map/map.cpp
@@ -0,0 +1,745 @@
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/view.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/map/source.hpp>
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/map/sprite.hpp>
+#include <mbgl/util/transition.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/clip_ids.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/uv_detail.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/text/glyph_store.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/style/style_layer_group.hpp>
+#include <mbgl/style/style_bucket.hpp>
+#include <mbgl/util/texture_pool.hpp>
+#include <mbgl/geometry/sprite_atlas.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/uv.hpp>
+
+#include <algorithm>
+#include <iostream>
+
+#define _USE_MATH_DEFINES
+#include <cmath>
+
+#include <uv.h>
+
+#ifdef __ANDROID__
+ #include <coffeecatch/coffeecatch.h>
+#endif
+
+// Check libuv library version.
+const static bool uv_version_check = []() {
+ const unsigned int version = uv_version();
+ const unsigned int major = (version >> 16) & 0xFF;
+ const unsigned int minor = (version >> 8) & 0xFF;
+ const unsigned int patch = version & 0xFF;
+
+#ifndef UV_VERSION_PATCH
+ // 0.10 doesn't have UV_VERSION_PATCH defined, so we "fake" it by using the library patch level.
+ const unsigned int UV_VERSION_PATCH = version & 0xFF;
+#endif
+
+ if (major != UV_VERSION_MAJOR || minor != UV_VERSION_MINOR || patch != UV_VERSION_PATCH) {
+ throw std::runtime_error(mbgl::util::sprintf<96>(
+ "libuv version mismatch: headers report %d.%d.%d, but library reports %d.%d.%d", UV_VERSION_MAJOR,
+ UV_VERSION_MINOR, UV_VERSION_PATCH, major, minor, patch));
+ }
+ return true;
+}();
+
+
+#include <zlib.h>
+// Check zlib library version.
+const static bool zlib_version_check = []() {
+ const char *const version = zlibVersion();
+ if (version[0] != ZLIB_VERSION[0]) {
+ throw std::runtime_error(mbgl::util::sprintf<96>(
+ "zlib version mismatch: headers report %s, but library reports %s", ZLIB_VERSION, version));
+ }
+
+ return true;
+}();
+
+
+#include <sqlite3.h>
+// Check sqlite3 library version.
+const static bool sqlite_version_check = []() {
+ if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER) {
+ throw std::runtime_error(mbgl::util::sprintf<96>(
+ "sqlite3 libversion mismatch: headers report %d, but library reports %d",
+ SQLITE_VERSION_NUMBER, sqlite3_libversion_number()));
+ }
+ if (strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) != 0) {
+ throw std::runtime_error(mbgl::util::sprintf<256>(
+ "sqlite3 sourceid mismatch: headers report \"%s\", but library reports \"%s\"",
+ SQLITE_SOURCE_ID, sqlite3_sourceid()));
+ }
+
+ return true;
+}();
+
+
+using namespace mbgl;
+
+Map::Map(View& view_, FileSource& fileSource_)
+ : loop(util::make_unique<uv::loop>()),
+ view(view_),
+#ifndef NDEBUG
+ mainThread(std::this_thread::get_id()),
+#endif
+ transform(view_),
+ fileSource(fileSource_),
+ glyphAtlas(util::make_unique<GlyphAtlas>(1024, 1024)),
+ glyphStore(std::make_shared<GlyphStore>(fileSource)),
+ spriteAtlas(util::make_unique<SpriteAtlas>(512, 512)),
+ texturePool(std::make_shared<TexturePool>()),
+ painter(util::make_unique<Painter>(*spriteAtlas, *glyphAtlas))
+{
+ view.initialize(this);
+ // Make sure that we're doing an initial drawing in all cases.
+ isClean.clear();
+ isRendered.clear();
+ isSwapped.test_and_set();
+}
+
+Map::~Map() {
+ if (async) {
+ stop();
+ }
+
+ // Explicitly reset all pointers.
+ activeSources.clear();
+ sprite.reset();
+ glyphStore.reset();
+ style.reset();
+ texturePool.reset();
+ workers.reset();
+
+ uv_run(**loop, UV_RUN_DEFAULT);
+}
+
+uv::worker &Map::getWorker() {
+ if (!workers) {
+ workers = util::make_unique<uv::worker>(**loop, 4, "Tile Worker");
+ }
+ return *workers;
+}
+
+void Map::start(bool startPaused) {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(!async);
+
+ // When starting map rendering in another thread, we perform async/continuously
+ // updated rendering. Only in these cases, we attach the async handlers.
+ async = true;
+
+ // Reset the flag.
+ isStopped = false;
+
+ // Setup async notifications
+ asyncTerminate = util::make_unique<uv::async>(**loop, [this]() {
+ assert(std::this_thread::get_id() == mapThread);
+
+ // Remove all of these to make sure they are destructed in the correct thread.
+ style.reset();
+ workers.reset();
+ activeSources.clear();
+
+ fileSource.clearLoop();
+
+ terminating = true;
+
+ // Closes all open handles on the loop. This means that the loop will automatically terminate.
+ asyncCleanup.reset();
+ asyncRender.reset();
+ asyncTerminate.reset();
+ });
+
+ asyncRender = util::make_unique<uv::async>(**loop, [this]() {
+ assert(std::this_thread::get_id() == mapThread);
+
+ if (state.hasSize()) {
+ if (isRendered.test_and_set() == false) {
+ prepare();
+ if (isClean.test_and_set() == false) {
+ render();
+ isSwapped.clear();
+ view.swap();
+ } else {
+ // We set the rendered flag in the test above, so we have to reset it
+ // now that we're not actually rendering because the map is clean.
+ isRendered.clear();
+ }
+ }
+ }
+ });
+
+ asyncCleanup = util::make_unique<uv::async>(**loop, [this]() {
+ assert(painter);
+ painter->cleanup();
+ });
+
+ // Do we need to pause first?
+ if (startPaused) {
+ pause();
+ }
+
+ thread = std::thread([this]() {
+#ifndef NDEBUG
+ mapThread = std::this_thread::get_id();
+#endif
+
+#ifdef __APPLE__
+ pthread_setname_np("Map");
+#endif
+
+ run();
+
+#ifndef NDEBUG
+ mapThread = std::thread::id();
+#endif
+
+ // Make sure that the stop() function knows when to stop invoking the callback function.
+ isStopped = true;
+ view.notify();
+ });
+}
+
+void Map::stop(std::function<void ()> callback) {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(mainThread != mapThread);
+ assert(async);
+
+ asyncTerminate->send();
+
+ resume();
+
+ if (callback) {
+ // Wait until the render thread stopped. We are using this construct instead of plainly
+ // relying on the thread_join because the system might need to run things in the current
+ // thread that is required for the render thread to terminate correctly. This is for example
+ // the case with Cocoa's NSURLRequest. Otherwise, we will eventually deadlock because this
+ // thread (== main thread) is blocked. The callback function should use an efficient waiting
+ // function to avoid a busy waiting loop.
+ while (!isStopped) {
+ callback();
+ }
+ }
+
+ // If a callback function was provided, this should return immediately because the thread has
+ // already finished executing.
+ thread.join();
+
+ async = false;
+}
+
+void Map::pause(bool waitForPause) {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(async);
+ mutexRun.lock();
+ pausing = true;
+ mutexRun.unlock();
+
+ uv_stop(**loop);
+ rerender(); // Needed to ensure uv_stop is seen and uv_run exits, otherwise we deadlock on wait_for_pause
+
+ if (waitForPause) {
+ std::unique_lock<std::mutex> lockPause (mutexPause);
+ while (!isPaused) {
+ condPause.wait(lockPause);
+ }
+ }
+}
+
+void Map::resume() {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(async);
+
+ mutexRun.lock();
+ pausing = false;
+ condRun.notify_all();
+ mutexRun.unlock();
+}
+
+void Map::run() {
+#ifdef __ANDROID__
+ COFFEE_TRY() {
+#endif
+
+#ifndef NDEBUG
+ if (!async) {
+ mapThread = mainThread;
+ }
+#endif
+ assert(std::this_thread::get_id() == mapThread);
+
+ if (async) {
+ checkForPause();
+ }
+
+ setup();
+ prepare();
+
+ if (async) {
+ terminating = false;
+ while(!terminating) {
+ uv_run(**loop, UV_RUN_DEFAULT);
+ checkForPause();
+ }
+ } else {
+ uv_run(**loop, UV_RUN_DEFAULT);
+ }
+
+ // Run the event loop once more to make sure our async delete handlers are called.
+ uv_run(**loop, UV_RUN_ONCE);
+
+ // If the map rendering wasn't started asynchronously, we perform one render
+ // *after* all events have been processed.
+ if (!async) {
+ render();
+#ifndef NDEBUG
+ mapThread = std::thread::id();
+#endif
+ }
+#ifdef __ANDROID__
+ } COFFEE_CATCH() {
+ Log::Error(Event::Crash, "Map::run() crash:\n%s", coffeecatch_get_message());
+ abort();
+ }
+#endif
+}
+
+void Map::checkForPause() {
+ std::unique_lock<std::mutex> lockRun (mutexRun);
+ while (pausing) {
+ view.make_inactive();
+
+ mutexPause.lock();
+ isPaused = true;
+ condPause.notify_all();
+ mutexPause.unlock();
+
+ condRun.wait(lockRun);
+
+ view.make_active();
+ }
+
+ mutexPause.lock();
+ isPaused = false;
+ mutexPause.unlock();
+}
+
+void Map::rerender() {
+ // We only send render events if we want to continuously update the map
+ // (== async rendering).
+ if (async) {
+ asyncRender->send();
+ }
+}
+
+void Map::update() {
+ isClean.clear();
+ rerender();
+}
+
+bool Map::needsSwap() {
+ return isSwapped.test_and_set() == false;
+}
+
+void Map::swapped() {
+ isRendered.clear();
+ rerender();
+}
+
+void Map::cleanup() {
+ if (asyncCleanup != nullptr) {
+ asyncCleanup->send();
+ }
+}
+
+void Map::terminate() {
+ assert(painter);
+ painter->terminate();
+}
+
+#pragma mark - Setup
+
+void Map::setup() {
+ assert(std::this_thread::get_id() == mapThread);
+ assert(painter);
+ view.make_active();
+ painter->setup();
+}
+
+void Map::setStyleURL(const std::string &url) {
+ // TODO: Make threadsafe.
+
+ styleURL = url;
+ if (async) {
+ stop();
+ start();
+ }
+}
+
+
+void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) {
+ // TODO: Make threadsafe.
+ styleJSON.swap(newStyleJSON);
+ sprite.reset();
+ if (!style) {
+ style = std::make_shared<Style>();
+ }
+
+ style->loadJSON((const uint8_t *)styleJSON.c_str());
+ fileSource.setBase(base);
+ glyphStore->setURL(style->glyph_url);
+
+ style->setDefaultTransitionDuration(defaultTransitionDuration);
+
+ // set applied classes if they were set while the style was loading
+ appliedClassesMutex.lock();
+ util::ptr<std::vector<std::string>> classes = appliedClasses;
+ if (appliedClasses) {
+ appliedClasses.reset();
+ }
+ appliedClassesMutex.unlock();
+ if (classes) {
+ style->setAppliedClasses(*classes);
+ }
+
+ update();
+}
+
+std::string Map::getStyleJSON() const {
+ return styleJSON;
+}
+
+util::ptr<Sprite> Map::getSprite() {
+ const float pixelRatio = state.getPixelRatio();
+ const std::string &sprite_url = style->getSpriteURL();
+ if (!sprite || sprite->pixelRatio != pixelRatio) {
+ sprite = Sprite::Create(sprite_url, pixelRatio, fileSource);
+ }
+
+ return sprite;
+}
+
+
+#pragma mark - Size
+
+void Map::resize(uint16_t width, uint16_t height, float ratio) {
+ resize(width, height, ratio, width * ratio, height * ratio);
+}
+
+void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight) {
+ if (transform.resize(width, height, ratio, fbWidth, fbHeight)) {
+ update();
+ }
+}
+
+#pragma mark - Transitions
+
+void Map::cancelTransitions() {
+ transform.cancelTransitions();
+
+ update();
+}
+
+
+#pragma mark - Position
+
+void Map::moveBy(double dx, double dy, double duration) {
+ transform.moveBy(dx, dy, duration * 1_second);
+ update();
+}
+
+void Map::setLonLat(double lon, double lat, double duration) {
+ transform.setLonLat(lon, lat, duration * 1_second);
+ update();
+}
+
+void Map::getLonLat(double& lon, double& lat) const {
+ transform.getLonLat(lon, lat);
+}
+
+void Map::startPanning() {
+ transform.startPanning();
+ update();
+}
+
+void Map::stopPanning() {
+ transform.stopPanning();
+ update();
+}
+
+void Map::resetPosition() {
+ transform.setAngle(0);
+ transform.setLonLat(0, 0);
+ transform.setZoom(0);
+ update();
+}
+
+
+#pragma mark - Scale
+
+void Map::scaleBy(double ds, double cx, double cy, double duration) {
+ transform.scaleBy(ds, cx, cy, duration * 1_second);
+ update();
+}
+
+void Map::setScale(double scale, double cx, double cy, double duration) {
+ transform.setScale(scale, cx, cy, duration * 1_second);
+ update();
+}
+
+double Map::getScale() const {
+ return transform.getScale();
+}
+
+void Map::setZoom(double zoom, double duration) {
+ transform.setZoom(zoom, duration * 1_second);
+ update();
+}
+
+double Map::getZoom() const {
+ return transform.getZoom();
+}
+
+void Map::setLonLatZoom(double lon, double lat, double zoom, double duration) {
+ transform.setLonLatZoom(lon, lat, zoom, duration * 1_second);
+ update();
+}
+
+void Map::getLonLatZoom(double& lon, double& lat, double& zoom) const {
+ transform.getLonLatZoom(lon, lat, zoom);
+}
+
+void Map::resetZoom() {
+ setZoom(0);
+}
+
+void Map::startScaling() {
+ transform.startScaling();
+ update();
+}
+
+void Map::stopScaling() {
+ transform.stopScaling();
+ update();
+}
+
+double Map::getMinZoom() const {
+ return transform.getMinZoom();
+}
+
+double Map::getMaxZoom() const {
+ return transform.getMaxZoom();
+}
+
+
+#pragma mark - Rotation
+
+void Map::rotateBy(double sx, double sy, double ex, double ey, double duration) {
+ transform.rotateBy(sx, sy, ex, ey, duration * 1_second);
+ update();
+}
+
+void Map::setBearing(double degrees, double duration) {
+ transform.setAngle(-degrees * M_PI / 180, duration * 1_second);
+ update();
+}
+
+void Map::setBearing(double degrees, double cx, double cy) {
+ transform.setAngle(-degrees * M_PI / 180, cx, cy);
+ update();
+}
+
+double Map::getBearing() const {
+ return -transform.getAngle() / M_PI * 180;
+}
+
+void Map::resetNorth() {
+ transform.setAngle(0, 500_milliseconds);
+ update();
+}
+
+void Map::startRotating() {
+ transform.startRotating();
+ update();
+}
+
+void Map::stopRotating() {
+ transform.stopRotating();
+ update();
+}
+
+
+#pragma mark - Toggles
+
+void Map::setDebug(bool value) {
+ debug = value;
+ assert(painter);
+ painter->setDebug(debug);
+ update();
+}
+
+void Map::toggleDebug() {
+ setDebug(!debug);
+}
+
+bool Map::getDebug() const {
+ return debug;
+}
+
+void Map::setAppliedClasses(const std::vector<std::string> &classes) {
+ if (style) {
+ style->setAppliedClasses(classes);
+ if (style->hasTransitions()) {
+ update();
+ }
+ }
+ else {
+ std::lock_guard<std::mutex> lock(appliedClassesMutex);
+ appliedClasses = mbgl::util::make_unique<std::vector<std::string>>(classes);
+ }
+}
+
+
+void Map::toggleClass(const std::string &name) {
+ style->toggleClass(name);
+ if (style->hasTransitions()) {
+ update();
+ }
+}
+
+const std::vector<std::string> &Map::getAppliedClasses() const {
+ return style->getAppliedClasses();
+}
+
+void Map::setDefaultTransitionDuration(uint64_t milliseconds) {
+ defaultTransitionDuration = milliseconds;
+ if (style) {
+ style->setDefaultTransitionDuration(milliseconds);
+ }
+}
+
+uint64_t Map::getDefaultTransitionDuration() {
+ return defaultTransitionDuration;
+}
+
+void Map::updateSources() {
+ assert(std::this_thread::get_id() == mapThread);
+
+ // First, disable all existing sources.
+ for (const auto& source : activeSources) {
+ source->enabled = false;
+ }
+
+ // Then, reenable all of those that we actually use when drawing this layer.
+ updateSources(style->layers);
+
+ // Then, construct or destroy the actual source object, depending on enabled state.
+ for (const auto& source : activeSources) {
+ if (source->enabled) {
+ if (!source->source) {
+ source->source = std::make_shared<Source>(source->info);
+ source->source->load(*this, fileSource);
+ }
+ } else {
+ source->source.reset();
+ }
+ }
+
+ // Finally, remove all sources that are disabled.
+ util::erase_if(activeSources, [](util::ptr<StyleSource> source){
+ return !source->enabled;
+ });
+}
+
+void Map::updateSources(const util::ptr<StyleLayerGroup> &group) {
+ if (!group) {
+ return;
+ }
+ for (const util::ptr<StyleLayer> &layer : group->layers) {
+ if (!layer) continue;
+ if (layer->bucket) {
+ if (layer->bucket->style_source) {
+ (*activeSources.emplace(layer->bucket->style_source).first)->enabled = true;
+ }
+ } else if (layer->layers) {
+ updateSources(layer->layers);
+ }
+ }
+}
+
+void Map::updateTiles() {
+ for (const auto& source : activeSources) {
+ source->source->update(*this, getWorker(),
+ style, *glyphAtlas, *glyphStore,
+ *spriteAtlas, getSprite(),
+ *texturePool, fileSource, [this](){ update(); });
+ }
+}
+
+void Map::prepare() {
+ if (!fileSource.hasLoop()) {
+ fileSource.setLoop(**loop);
+ }
+
+ if (!style) {
+ style = std::make_shared<Style>();
+
+ fileSource.request(ResourceType::JSON, styleURL)->onload([&](const Response &res) {
+ if (res.code == 200) {
+ // Calculate the base
+ const size_t pos = styleURL.rfind('/');
+ std::string base = "";
+ if (pos != std::string::npos) {
+ base = styleURL.substr(0, pos + 1);
+ }
+
+ setStyleJSON(res.data, base);
+ } else {
+ Log::Error(Event::Setup, "loading style failed: %ld (%s)", res.code, res.message.c_str());
+ }
+ });
+ }
+
+ // Update transform transitions.
+ animationTime = util::now();
+ if (transform.needsTransition()) {
+ transform.updateTransitions(animationTime);
+ }
+
+ state = transform.currentState();
+
+ animationTime = util::now();
+ updateSources();
+ style->updateProperties(state.getNormalizedZoom(), animationTime);
+
+ // Allow the sprite atlas to potentially pull new sprite images if needed.
+ spriteAtlas->resize(state.getPixelRatio());
+ spriteAtlas->setSprite(getSprite());
+
+ updateTiles();
+}
+
+void Map::render() {
+ assert(painter);
+ painter->render(*style, activeSources,
+ state, animationTime);
+ // Schedule another rerender when we definitely need a next frame.
+ if (transform.needsTransition() || style->hasTransitions()) {
+ update();
+ }
+}
diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp
new file mode 100644
index 0000000000..6fac7862e7
--- /dev/null
+++ b/src/mbgl/map/raster_tile_data.cpp
@@ -0,0 +1,34 @@
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/raster_tile_data.hpp>
+#include <mbgl/style/style.hpp>
+
+using namespace mbgl;
+
+
+RasterTileData::RasterTileData(Tile::ID const& id_, TexturePool& texturePool, const SourceInfo& source_)
+ : TileData(id_, source_),
+ bucket(texturePool, properties) {
+}
+
+RasterTileData::~RasterTileData() {
+}
+
+void RasterTileData::parse() {
+ if (state != State::loaded) {
+ return;
+ }
+
+ if (bucket.setImage(data)) {
+ state = State::parsed;
+ } else {
+ state = State::invalid;
+ }
+}
+
+void RasterTileData::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) {
+ bucket.render(painter, layer_desc, id, matrix);
+}
+
+bool RasterTileData::hasData(StyleLayer const& /*layer_desc*/) const {
+ return bucket.hasData();
+}
diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp
new file mode 100644
index 0000000000..42070d9c61
--- /dev/null
+++ b/src/mbgl/map/raster_tile_data.hpp
@@ -0,0 +1,33 @@
+#ifndef MBGL_MAP_RASTER_TILE_DATA
+#define MBGL_MAP_RASTER_TILE_DATA
+
+#include <mbgl/map/tile.hpp>
+#include <mbgl/map/tile_data.hpp>
+#include <mbgl/renderer/raster_bucket.hpp>
+
+namespace mbgl {
+
+class Painter;
+class SourceInfo;
+class StyleLayer;
+class TexturePool;
+
+class RasterTileData : public TileData {
+ friend class TileParser;
+
+public:
+ RasterTileData(Tile::ID const& id, TexturePool&, const SourceInfo&);
+ ~RasterTileData();
+
+ virtual void parse();
+ virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix);
+ virtual bool hasData(StyleLayer const& layer_desc) const;
+
+protected:
+ StyleBucketRaster properties;
+ RasterBucket bucket;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp
new file mode 100644
index 0000000000..798cd41d1d
--- /dev/null
+++ b/src/mbgl/map/source.cpp
@@ -0,0 +1,369 @@
+#include <mbgl/map/source.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/transform.hpp>
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/raster.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/texture_pool.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/util/vec.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/box.hpp>
+#include <mbgl/util/mapbox.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <mbgl/map/vector_tile_data.hpp>
+#include <mbgl/map/raster_tile_data.hpp>
+
+#include <algorithm>
+
+namespace mbgl {
+
+Source::Source(SourceInfo& info_)
+ : info(info_)
+{
+}
+
+// Note: This is a separate function that must be called exactly once after creation
+// The reason this isn't part of the constructor is that calling shared_from_this() in
+// the constructor fails.
+void Source::load(Map& map, FileSource& fileSource) {
+ if (info.url.empty()) {
+ loaded = true;
+ return;
+ }
+
+ util::ptr<Source> source = shared_from_this();
+
+ fileSource.request(ResourceType::JSON, info.url)->onload([source, &map](const Response &res) {
+ if (res.code != 200) {
+ Log::Warning(Event::General, "failed to load source TileJSON");
+ return;
+ }
+
+ rapidjson::Document d;
+ d.Parse<0>(res.data.c_str());
+
+ if (d.HasParseError()) {
+ Log::Warning(Event::General, "invalid source TileJSON");
+ return;
+ }
+
+ source->info.parseTileJSONProperties(d);
+ source->loaded = true;
+
+ map.update();
+
+ });
+}
+
+void Source::updateClipIDs(const std::map<Tile::ID, ClipID> &mapping) {
+ std::for_each(tiles.begin(), tiles.end(), [&mapping](std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
+ Tile &tile = *pair.second;
+ auto it = mapping.find(tile.id);
+ if (it != mapping.end()) {
+ tile.clip = it->second;
+ } else {
+ tile.clip = ClipID {};
+ }
+ });
+}
+
+void Source::updateMatrices(const mat4 &projMatrix, const TransformState &transform) {
+ for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
+ Tile &tile = *pair.second;
+ transform.matrixFor(tile.matrix, tile.id);
+ matrix::multiply(tile.matrix, projMatrix, tile.matrix);
+ }
+}
+
+size_t Source::getTileCount() const {
+ return tiles.size();
+}
+
+void Source::drawClippingMasks(Painter &painter) {
+ for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
+ Tile &tile = *pair.second;
+ gl::group group(std::string { "mask: " } + std::string(tile.id));
+ painter.drawClippingMask(tile.matrix, tile.clip);
+ }
+}
+
+void Source::render(Painter &painter, util::ptr<StyleLayer> layer_desc) {
+ gl::group group(std::string { "layer: " } + layer_desc->id);
+ for (const std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
+ Tile &tile = *pair.second;
+ if (tile.data && tile.data->state == TileData::State::parsed) {
+ painter.renderTileLayer(tile, layer_desc, tile.matrix);
+ }
+ }
+}
+
+void Source::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) {
+ auto it = tiles.find(id);
+ if (it != tiles.end() && it->second->data && it->second->data->state == TileData::State::parsed) {
+ painter.renderTileLayer(*it->second, layer_desc, matrix);
+ }
+}
+
+void Source::finishRender(Painter &painter) {
+ for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
+ Tile &tile = *pair.second;
+ painter.renderTileDebug(tile);
+ }
+}
+
+std::forward_list<Tile::ID> Source::getIDs() const {
+ std::forward_list<Tile::ID> ptrs;
+
+ std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
+ Tile &tile = *pair.second;
+ return tile.id;
+ });
+ return ptrs;
+}
+
+std::forward_list<Tile *> Source::getLoadedTiles() const {
+ std::forward_list<Tile *> ptrs;
+ auto it = ptrs.before_begin();
+ for (const auto &pair : tiles) {
+ if (pair.second->data->ready()) {
+ it = ptrs.insert_after(it, pair.second.get());
+ }
+ }
+ return ptrs;
+}
+
+
+TileData::State Source::hasTile(const Tile::ID& id) {
+ auto it = tiles.find(id);
+ if (it != tiles.end()) {
+ Tile &tile = *it->second;
+ if (tile.id == id && tile.data) {
+ return tile.data->state;
+ }
+ }
+
+ return TileData::State::invalid;
+}
+
+TileData::State Source::addTile(Map& map, uv::worker& worker,
+ util::ptr<Style> style,
+ GlyphAtlas& glyphAtlas, GlyphStore& glyphStore,
+ SpriteAtlas& spriteAtlas, util::ptr<Sprite> sprite,
+ FileSource& fileSource, TexturePool& texturePool,
+ const Tile::ID& id,
+ std::function<void ()> callback) {
+ const TileData::State state = hasTile(id);
+
+ if (state != TileData::State::invalid) {
+ return state;
+ }
+
+ auto pos = tiles.emplace(id, util::make_unique<Tile>(id));
+ Tile& new_tile = *pos.first->second;
+
+ // We couldn't find the tile in the list. Create a new one.
+ // Try to find the associated TileData object.
+ const Tile::ID normalized_id = id.normalized();
+
+ auto it = tile_data.find(normalized_id);
+ if (it != tile_data.end()) {
+ // Create a shared_ptr handle. Note that this might be empty!
+ new_tile.data = it->second.lock();
+ }
+
+ if (new_tile.data && new_tile.data->state == TileData::State::obsolete) {
+ // Do not consider the tile if it's already obsolete.
+ new_tile.data.reset();
+ }
+
+ if (!new_tile.data) {
+ // If we don't find working tile data, we're just going to load it.
+ if (info.type == SourceType::Vector) {
+ new_tile.data = std::make_shared<VectorTileData>(normalized_id, map.getMaxZoom(), style,
+ glyphAtlas, glyphStore,
+ spriteAtlas, sprite,
+ texturePool, info);
+ } else if (info.type == SourceType::Raster) {
+ new_tile.data = std::make_shared<RasterTileData>(normalized_id, texturePool, info);
+ } else {
+ throw std::runtime_error("source type not implemented");
+ }
+
+ new_tile.data->request(worker, fileSource, map.getState().getPixelRatio(), callback);
+ tile_data.emplace(new_tile.data->id, new_tile.data);
+ }
+
+ return new_tile.data->state;
+}
+
+double Source::getZoom(const TransformState& state) const {
+ double offset = std::log(util::tileSize / info.tile_size) / std::log(2);
+ offset += (state.getPixelRatio() > 1.0 ? 1 :0);
+ return state.getZoom() + offset;
+}
+
+int32_t Source::coveringZoomLevel(const TransformState& state) const {
+ return std::floor(getZoom(state));
+}
+
+std::forward_list<Tile::ID> Source::coveringTiles(const TransformState& state) const {
+ int32_t z = coveringZoomLevel(state);
+
+ if (z < info.min_zoom) return {{}};
+ if (z > info.max_zoom) z = info.max_zoom;
+
+ // Map four viewport corners to pixel coordinates
+ box points = state.cornersToBox(z);
+ const vec2<double>& center = points.center;
+
+ std::forward_list<Tile::ID> covering_tiles = Tile::cover(z, points);
+
+ covering_tiles.sort([&center](const Tile::ID& a, const Tile::ID& b) {
+ // Sorts by distance from the box center
+ return std::fabs(a.x - center.x) + std::fabs(a.y - center.y) <
+ std::fabs(b.x - center.x) + std::fabs(b.y - center.y);
+ });
+
+ return covering_tiles;
+}
+
+/**
+ * Recursively find children of the given tile that are already loaded.
+ *
+ * @param id The tile ID that we should find children for.
+ * @param maxCoveringZoom The maximum zoom level of children to look for.
+ * @param retain An object that we add the found tiles to.
+ *
+ * @return boolean Whether the children found completely cover the tile.
+ */
+bool Source::findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain) {
+ bool complete = true;
+ int32_t z = id.z;
+ auto ids = id.children(z + 1);
+ for (const Tile::ID& child_id : ids) {
+ const TileData::State state = hasTile(child_id);
+ if (state == TileData::State::parsed) {
+ retain.emplace_front(child_id);
+ } else {
+ complete = false;
+ if (z < maxCoveringZoom) {
+ // Go further down the hierarchy to find more unloaded children.
+ findLoadedChildren(child_id, maxCoveringZoom, retain);
+ }
+ }
+ }
+ return complete;
+}
+
+/**
+ * Find a loaded parent of the given tile.
+ *
+ * @param id The tile ID that we should find children for.
+ * @param minCoveringZoom The minimum zoom level of parents to look for.
+ * @param retain An object that we add the found tiles to.
+ *
+ * @return boolean Whether a parent was found.
+ */
+bool Source::findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain) {
+ for (int32_t z = id.z - 1; z >= minCoveringZoom; --z) {
+ const Tile::ID parent_id = id.parent(z);
+ const TileData::State state = hasTile(parent_id);
+ if (state == TileData::State::parsed) {
+ retain.emplace_front(parent_id);
+ return true;
+ }
+ }
+ return false;
+}
+
+void Source::update(Map& map, uv::worker& worker,
+ util::ptr<Style> style,
+ GlyphAtlas& glyphAtlas, GlyphStore& glyphStore,
+ SpriteAtlas& spriteAtlas, util::ptr<Sprite> sprite,
+ TexturePool& texturePool, FileSource& fileSource,
+ std::function<void ()> callback) {
+ if (!loaded || map.getTime() <= updated)
+ return;
+
+ bool changed = false;
+
+ int32_t zoom = std::floor(getZoom(map.getState()));
+ std::forward_list<Tile::ID> required = coveringTiles(map.getState());
+
+ // Determine the overzooming/underzooming amounts.
+ int32_t minCoveringZoom = util::clamp<int32_t>(zoom - 10, info.min_zoom, info.max_zoom);
+ int32_t maxCoveringZoom = util::clamp<int32_t>(zoom + 1, info.min_zoom, info.max_zoom);
+
+ // Retain is a list of tiles that we shouldn't delete, even if they are not
+ // the most ideal tile for the current viewport. This may include tiles like
+ // parent or child tiles that are *already* loaded.
+ std::forward_list<Tile::ID> retain(required);
+
+ // Add existing child/parent tiles if the actual tile is not yet loaded
+ for (const Tile::ID& id : required) {
+ const TileData::State state = addTile(map, worker, style,
+ glyphAtlas, glyphStore,
+ spriteAtlas, sprite,
+ fileSource, texturePool,
+ id, callback);
+
+ if (state != TileData::State::parsed) {
+ // The tile we require is not yet loaded. Try to find a parent or
+ // child tile that we already have.
+
+ // First, try to find existing child tiles that completely cover the
+ // missing tile.
+ bool complete = findLoadedChildren(id, maxCoveringZoom, retain);
+
+ // Then, if there are no complete child tiles, try to find existing
+ // parent tiles that completely cover the missing tile.
+ if (!complete) {
+ findLoadedParent(id, minCoveringZoom, retain);
+ }
+ }
+
+ if (state == TileData::State::initial) {
+ changed = true;
+ }
+ }
+
+ // Remove tiles that we definitely don't need, i.e. tiles that are not on
+ // the required list.
+ std::set<Tile::ID> retain_data;
+ util::erase_if(tiles, [&retain, &retain_data, &changed](std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
+ Tile &tile = *pair.second;
+ bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end();
+ if (obsolete) {
+ changed = true;
+ } else {
+ retain_data.insert(tile.data->id);
+ }
+ return obsolete;
+ });
+
+ // Remove all the expired pointers from the set.
+ util::erase_if(tile_data, [&retain_data](std::pair<const Tile::ID, std::weak_ptr<TileData>> &pair) {
+ const util::ptr<TileData> tile = pair.second.lock();
+ if (!tile) {
+ return true;
+ }
+
+ bool obsolete = retain_data.find(tile->id) == retain_data.end();
+ if (obsolete) {
+ tile->cancel();
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ updated = map.getTime();
+}
+
+}
diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp
new file mode 100644
index 0000000000..8976f67b05
--- /dev/null
+++ b/src/mbgl/map/source.hpp
@@ -0,0 +1,86 @@
+#ifndef MBGL_MAP_SOURCE
+#define MBGL_MAP_SOURCE
+
+#include <mbgl/map/tile.hpp>
+#include <mbgl/map/tile_data.hpp>
+#include <mbgl/style/style_source.hpp>
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <cstdint>
+#include <forward_list>
+#include <iosfwd>
+#include <map>
+
+namespace mbgl {
+
+class Map;
+class GlyphAtlas;
+class GlyphStore;
+class SpriteAtlas;
+class Sprite;
+class FileSource;
+class TexturePool;
+class Style;
+class Painter;
+class StyleLayer;
+class TransformState;
+struct box;
+
+class Source : public std::enable_shared_from_this<Source>, private util::noncopyable {
+public:
+ Source(SourceInfo&);
+
+ void load(Map&, FileSource&);
+ void update(Map&, uv::worker&,
+ util::ptr<Style>,
+ GlyphAtlas&, GlyphStore&,
+ SpriteAtlas&, util::ptr<Sprite>,
+ TexturePool&, FileSource&,
+ std::function<void ()> callback);
+
+ void updateMatrices(const mat4 &projMatrix, const TransformState &transform);
+ void drawClippingMasks(Painter &painter);
+ size_t getTileCount() const;
+ void render(Painter &painter, util::ptr<StyleLayer> layer_desc);
+ void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix);
+ void finishRender(Painter &painter);
+
+ std::forward_list<Tile::ID> getIDs() const;
+ std::forward_list<Tile *> getLoadedTiles() const;
+ void updateClipIDs(const std::map<Tile::ID, ClipID> &mapping);
+
+private:
+ bool findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain);
+ bool findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain);
+ int32_t coveringZoomLevel(const TransformState&) const;
+ std::forward_list<Tile::ID> coveringTiles(const TransformState&) const;
+
+ TileData::State addTile(Map&, uv::worker&,
+ util::ptr<Style>,
+ GlyphAtlas&, GlyphStore&,
+ SpriteAtlas&, util::ptr<Sprite>,
+ FileSource&, TexturePool&,
+ const Tile::ID&,
+ std::function<void ()> callback);
+
+ TileData::State hasTile(const Tile::ID& id);
+
+ double getZoom(const TransformState &state) const;
+
+ SourceInfo& info;
+ bool loaded = false;
+
+ // Stores the time when this source was most recently updated.
+ timestamp updated = 0;
+
+ std::map<Tile::ID, std::unique_ptr<Tile>> tiles;
+ std::map<Tile::ID, std::weak_ptr<TileData>> tile_data;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/sprite.cpp b/src/mbgl/map/sprite.cpp
new file mode 100644
index 0000000000..c1f71e59d9
--- /dev/null
+++ b/src/mbgl/map/sprite.cpp
@@ -0,0 +1,152 @@
+#include <mbgl/map/sprite.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/util/raster.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <string>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/util/uv_detail.hpp>
+#include <mbgl/util/std.hpp>
+
+#include <rapidjson/document.h>
+
+using namespace mbgl;
+
+SpritePosition::SpritePosition(uint16_t x_, uint16_t y_, uint16_t width_, uint16_t height_, float pixelRatio_, bool sdf_)
+ : x(x_),
+ y(y_),
+ width(width_),
+ height(height_),
+ pixelRatio(pixelRatio_),
+ sdf(sdf_) {
+}
+
+util::ptr<Sprite> Sprite::Create(const std::string& base_url, float pixelRatio, FileSource& fileSource) {
+ util::ptr<Sprite> sprite(std::make_shared<Sprite>(Key(), base_url, pixelRatio));
+ sprite->load(fileSource);
+ return sprite;
+}
+
+Sprite::Sprite(const Key &, const std::string& base_url, float pixelRatio_)
+ : valid(base_url.length() > 0),
+ pixelRatio(pixelRatio_),
+ spriteURL(base_url + (pixelRatio_ > 1 ? "@2x" : "") + ".png"),
+ jsonURL(base_url + (pixelRatio_ > 1 ? "@2x" : "") + ".json"),
+ raster(),
+ loadedImage(false),
+ loadedJSON(false),
+ future(promise.get_future()) {
+}
+
+void Sprite::waitUntilLoaded() const {
+ future.wait();
+}
+
+Sprite::operator bool() const {
+ return valid && isLoaded() && !pos.empty();
+}
+
+
+// Note: This is a separate function that must be called exactly once after creation
+// The reason this isn't part of the constructor is that calling shared_from_this() in
+// the constructor fails.
+void Sprite::load(FileSource& fileSource) {
+ if (!valid) {
+ // Treat a non-existent sprite as a successfully loaded empty sprite.
+ loadedImage = true;
+ loadedJSON = true;
+ promise.set_value();
+ return;
+ }
+
+ util::ptr<Sprite> sprite = shared_from_this();
+
+ fileSource.request(ResourceType::JSON, jsonURL)->onload([sprite](const Response &res) {
+ if (res.code == 200) {
+ sprite->body = res.data;
+ sprite->parseJSON();
+ sprite->complete();
+ } else {
+ Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res.code, res.message.c_str());
+ if (!sprite->future.valid()) {
+ sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message)));
+ }
+ }
+ });
+
+ fileSource.request(ResourceType::Image, spriteURL)->onload([sprite](const Response &res) {
+ if (res.code == 200) {
+ sprite->image = res.data;
+ sprite->parseImage();
+ sprite->complete();
+ } else {
+ Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res.code, res.message.c_str());
+ if (!sprite->future.valid()) {
+ sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message)));
+ }
+ }
+ });
+}
+
+void Sprite::complete() {
+ if (loadedImage && loadedJSON) {
+ Log::Info(Event::Sprite, "loaded %s", spriteURL.c_str());
+ promise.set_value();
+ }
+}
+
+bool Sprite::isLoaded() const {
+ return loadedImage && loadedJSON;
+}
+
+void Sprite::parseImage() {
+ raster = util::make_unique<util::Image>(image);
+ if (!*raster) {
+ raster.reset();
+ }
+ image.clear();
+ loadedImage = true;
+}
+
+void Sprite::parseJSON() {
+ rapidjson::Document d;
+ d.Parse<0>(body.c_str());
+ body.clear();
+
+ if (d.HasParseError()) {
+ Log::Warning(Event::Sprite, "sprite JSON is invalid");
+ } else if (d.IsObject()) {
+ for (rapidjson::Value::ConstMemberIterator itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr) {
+ const std::string& name = itr->name.GetString();
+ const rapidjson::Value& value = itr->value;
+
+ if (value.IsObject()) {
+ uint16_t x = 0;
+ uint16_t y = 0;
+ uint16_t width = 0;
+ uint16_t height = 0;
+ float spritePixelRatio = 1.0f;
+ bool sdf = false;
+
+ if (value.HasMember("x")) x = value["x"].GetInt();
+ if (value.HasMember("y")) y = value["y"].GetInt();
+ if (value.HasMember("width")) width = value["width"].GetInt();
+ if (value.HasMember("height")) height = value["height"].GetInt();
+ if (value.HasMember("pixelRatio")) spritePixelRatio = value["pixelRatio"].GetInt();
+ if (value.HasMember("sdf")) sdf = value["sdf"].GetBool();
+ pos.emplace(name, SpritePosition { x, y, width, height, spritePixelRatio, sdf });
+ }
+ }
+ } else {
+ Log::Warning(Event::Sprite, "sprite JSON root is not an object");
+ }
+
+ loadedJSON = true;
+}
+
+const SpritePosition &Sprite::getSpritePosition(const std::string& name) const {
+ if (!isLoaded()) return empty;
+ auto it = pos.find(name);
+ return it == pos.end() ? empty : it->second;
+}
diff --git a/src/mbgl/map/sprite.hpp b/src/mbgl/map/sprite.hpp
new file mode 100644
index 0000000000..d4b54ba1b5
--- /dev/null
+++ b/src/mbgl/map/sprite.hpp
@@ -0,0 +1,79 @@
+#ifndef MBGL_STYLE_SPRITE
+#define MBGL_STYLE_SPRITE
+
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <cstdint>
+#include <atomic>
+#include <iosfwd>
+#include <string>
+#include <unordered_map>
+#include <future>
+
+namespace mbgl {
+
+class FileSource;
+
+class SpritePosition {
+public:
+ explicit SpritePosition() {}
+ explicit SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t height, float pixelRatio, bool sdf);
+
+ operator bool() const {
+ return !(width == 0 && height == 0 && x == 0 && y == 0);
+ }
+
+ uint16_t x = 0, y = 0;
+ uint16_t width = 0, height = 0;
+ float pixelRatio = 1.0f;
+ bool sdf = false;
+};
+
+class Sprite : public std::enable_shared_from_this<Sprite>, private util::noncopyable {
+private:
+ struct Key {};
+ void load(FileSource& fileSource);
+
+public:
+ Sprite(const Key &, const std::string& base_url, float pixelRatio);
+ static util::ptr<Sprite> Create(const std::string& base_url, float pixelRatio, FileSource& fileSource);
+
+ const SpritePosition &getSpritePosition(const std::string& name) const;
+
+ void waitUntilLoaded() const;
+ bool isLoaded() const;
+
+ operator bool() const;
+
+private:
+ const bool valid;
+
+public:
+ const float pixelRatio;
+ const std::string spriteURL;
+ const std::string jsonURL;
+ std::unique_ptr<util::Image> raster;
+
+private:
+ void parseJSON();
+ void parseImage();
+ void complete();
+
+private:
+ std::string body;
+ std::string image;
+ std::atomic<bool> loadedImage;
+ std::atomic<bool> loadedJSON;
+ std::unordered_map<std::string, SpritePosition> pos;
+ const SpritePosition empty;
+
+ std::promise<void> promise;
+ std::future<void> future;
+
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/tile.cpp b/src/mbgl/map/tile.cpp
new file mode 100644
index 0000000000..9f31048857
--- /dev/null
+++ b/src/mbgl/map/tile.cpp
@@ -0,0 +1,147 @@
+#include <mbgl/map/tile.hpp>
+#include <mbgl/util/vec.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/box.hpp>
+
+
+#include <cassert>
+
+using namespace mbgl;
+
+#include <iostream>
+
+Tile::Tile(const ID& id_)
+ : id(id_) {
+}
+
+Tile::ID Tile::ID::parent(int8_t parent_z) const {
+ assert(parent_z < z);
+ int32_t dim = std::pow(2, z - parent_z);
+ return Tile::ID{
+ parent_z,
+ (x >= 0 ? x : x - dim + 1) / dim,
+ y / dim
+ };
+}
+
+std::forward_list<Tile::ID> Tile::ID::children(int32_t child_z) const {
+ assert(child_z > z);
+ int32_t factor = std::pow(2, child_z - z);
+
+ std::forward_list<ID> child_ids;
+ for (int32_t ty = y * factor, y_max = (y + 1) * factor; ty < y_max; ++ty) {
+ for (int32_t tx = x * factor, x_max = (x + 1) * factor; tx < x_max; ++tx) {
+ child_ids.emplace_front(child_z, tx, ty);
+ }
+ }
+ return child_ids;
+}
+
+Tile::ID Tile::ID::normalized() const {
+ int32_t dim = std::pow(2, z);
+ int32_t nx = x, ny = y;
+ while (nx < 0) nx += dim;
+ while (nx >= dim) nx -= dim;
+ return ID { z, nx, ny };
+}
+
+bool Tile::ID::isChildOf(const Tile::ID &parent_id) const {
+ if (parent_id.z >= z || parent_id.w != w) {
+ return false;
+ }
+ int32_t scale = std::pow(2, z - parent_id.z);
+ return parent_id.x == ((x < 0 ? x - scale + 1 : x) / scale) &&
+ parent_id.y == y / scale;
+}
+
+
+Tile::ID::operator std::string() const {
+ return util::toString(z) + "/" + util::toString(x) + "/" + util::toString(y);
+}
+
+
+// Taken from polymaps src/Layer.js
+// https://github.com/simplegeo/polymaps/blob/master/src/Layer.js#L333-L383
+
+struct edge {
+ double x0 = 0, y0 = 0;
+ double x1 = 0, y1 = 0;
+ double dx = 0, dy = 0;
+
+ edge(vec2<double> a, vec2<double> b) {
+ if (a.y > b.y) { std::swap(a, b); }
+ x0 = a.x;
+ y0 = a.y;
+ x1 = b.x;
+ y1 = b.y;
+ dx = b.x - a.x;
+ dy = b.y - a.y;
+ }
+};
+
+typedef const std::function<void(int32_t x0, int32_t x1, int32_t y)> ScanLine;
+
+// scan-line conversion
+static void scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine scanLine) {
+ double y0 = std::fmax(ymin, std::floor(e1.y0));
+ double y1 = std::fmin(ymax, std::ceil(e1.y1));
+
+ // sort edges by x-coordinate
+ if ((e0.x0 == e1.x0 && e0.y0 == e1.y0) ?
+ (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) :
+ (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) {
+ std::swap(e0, e1);
+ }
+
+ // scan lines!
+ double m0 = e0.dx / e0.dy;
+ double m1 = e1.dx / e1.dy;
+ double d0 = e0.dx > 0; // use y + 1 to compute x0
+ double d1 = e1.dx < 0; // use y + 1 to compute x1
+ for (int32_t y = y0; y < y1; y++) {
+ double x0 = m0 * std::fmax(0, std::fmin(e0.dy, y + d0 - e0.y0)) + e0.x0;
+ double x1 = m1 * std::fmax(0, std::fmin(e1.dy, y + d1 - e1.y0)) + e1.x0;
+ scanLine(std::floor(x1), std::ceil(x0), y);
+ }
+}
+
+// scan-line conversion
+static void scanTriangle(const mbgl::vec2<double> a, const mbgl::vec2<double> b, const mbgl::vec2<double> c, int32_t ymin, int32_t ymax, ScanLine& scanLine) {
+ edge ab = edge(a, b);
+ edge bc = edge(b, c);
+ edge ca = edge(c, a);
+
+ // sort edges by y-length
+ if (ab.dy > bc.dy) { std::swap(ab, bc); }
+ if (ab.dy > ca.dy) { std::swap(ab, ca); }
+ if (bc.dy > ca.dy) { std::swap(bc, ca); }
+
+ // scan span! scan span!
+ if (ab.dy) scanSpans(ca, ab, ymin, ymax, scanLine);
+ if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine);
+}
+
+std::forward_list<Tile::ID> Tile::cover(int8_t z, const mbgl::box &bounds) {
+ int32_t tiles = 1 << z;
+ std::forward_list<mbgl::Tile::ID> t;
+
+ auto scanLine = [&](int32_t x0, int32_t x1, int32_t y) {
+ int32_t x;
+ if (y >= 0 && y <= tiles) {
+ for (x = x0; x < x1; x++) {
+ t.emplace_front(z, x, y);
+ }
+ }
+ };
+
+ // Divide the screen up in two triangles and scan each of them:
+ // \---+
+ // | \ |
+ // +---\.
+ scanTriangle(bounds.tl, bounds.tr, bounds.br, 0, tiles, scanLine);
+ scanTriangle(bounds.br, bounds.bl, bounds.tl, 0, tiles, scanLine);
+
+ t.unique();
+
+ return t;
+}
diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp
new file mode 100644
index 0000000000..f89ff15baf
--- /dev/null
+++ b/src/mbgl/map/tile_data.cpp
@@ -0,0 +1,104 @@
+#include <mbgl/map/tile_data.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/style/style_source.hpp>
+
+#include <mbgl/util/token.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/util/uv_detail.hpp>
+
+using namespace mbgl;
+
+TileData::TileData(Tile::ID const& id_, const SourceInfo& source_)
+ : id(id_),
+ name(id),
+ state(State::initial),
+ source(source_),
+ debugBucket(debugFontBuffer) {
+ // Initialize tile debug coordinates
+ debugFontBuffer.addText(name.c_str(), 50, 200, 5);
+}
+
+TileData::~TileData() {
+ cancel();
+}
+
+const std::string TileData::toString() const {
+ return std::string { "[tile " } + name + "]";
+}
+
+void TileData::request(uv::worker& worker, FileSource& fileSource,
+ float pixelRatio, std::function<void ()> callback) {
+ if (source.tiles.empty())
+ return;
+
+ std::string url = source.tiles[(id.x + id.y) % source.tiles.size()];
+ url = util::replaceTokens(url, [&](const std::string &token) -> std::string {
+ if (token == "z") return util::toString(id.z);
+ if (token == "x") return util::toString(id.x);
+ if (token == "y") return util::toString(id.y);
+ if (token == "prefix") {
+ std::string prefix { 2 };
+ prefix[0] = "0123456789abcdef"[id.x % 16];
+ prefix[1] = "0123456789abcdef"[id.y % 16];
+ return prefix;
+ }
+ if (token == "ratio") return pixelRatio > 1.0 ? "@2x" : "";
+ return "";
+ });
+
+ state = State::loading;
+
+ // Note: Somehow this feels slower than the change to request_http()
+ std::weak_ptr<TileData> weak_tile = shared_from_this();
+ req = fileSource.request(ResourceType::Tile, url);
+ req->onload([weak_tile, url, callback, &worker](const Response &res) {
+ util::ptr<TileData> tile = weak_tile.lock();
+ if (!tile || tile->state == State::obsolete) {
+ // noop. Tile is obsolete and we're now just waiting for the refcount
+ // to drop to zero for destruction.
+ return;
+ }
+
+ // Clear the request object.
+ tile->req.reset();
+
+ if (res.code == 200) {
+ tile->state = State::loaded;
+
+ tile->data = res.data;
+
+ // Schedule tile parsing in another thread
+ tile->reparse(worker, callback);
+ } else {
+#if defined(DEBUG)
+ fprintf(stderr, "[%s] tile loading failed: %ld, %s\n", url.c_str(), res.code, res.message.c_str());
+#endif
+ }
+ });
+}
+
+void TileData::cancel() {
+ if (state != State::obsolete) {
+ state = State::obsolete;
+ if (req) {
+ req->cancel();
+ req.reset();
+ }
+ }
+}
+
+void TileData::reparse(uv::worker& worker, std::function<void()> callback)
+{
+ // We're creating a new work request. The work request deletes itself after it executed
+ // the after work handler
+ new uv::work<util::ptr<TileData>>(
+ worker,
+ [](util::ptr<TileData>& tile) {
+ tile->parse();
+ },
+ [callback](util::ptr<TileData>&) {
+ callback();
+ },
+ shared_from_this());
+}
diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp
new file mode 100644
index 0000000000..1ae215b204
--- /dev/null
+++ b/src/mbgl/map/tile_data.hpp
@@ -0,0 +1,88 @@
+#ifndef MBGL_MAP_TILE_DATA
+#define MBGL_MAP_TILE_DATA
+
+#include <mbgl/map/tile.hpp>
+#include <mbgl/renderer/debug_bucket.hpp>
+#include <mbgl/geometry/debug_font_buffer.hpp>
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <atomic>
+#include <exception>
+#include <iosfwd>
+#include <string>
+#include <functional>
+
+namespace uv {
+class worker;
+}
+
+namespace mbgl {
+
+class Map;
+class FileSource;
+class Painter;
+class SourceInfo;
+class StyleLayer;
+class Request;
+
+class TileData : public std::enable_shared_from_this<TileData>,
+ private util::noncopyable {
+public:
+ struct exception : std::exception {};
+ struct geometry_too_long_exception : exception {};
+
+public:
+ typedef util::ptr<TileData> Ptr;
+
+ enum class State {
+ invalid,
+ initial,
+ loading,
+ loaded,
+ parsed,
+ obsolete
+ };
+
+public:
+ TileData(Tile::ID const& id, const SourceInfo&);
+ ~TileData();
+
+ void request(uv::worker&, FileSource&, float pixelRatio, std::function<void ()> callback);
+ void reparse(uv::worker&, std::function<void ()> callback);
+ void cancel();
+ const std::string toString() const;
+
+ inline bool ready() const {
+ return state == State::parsed;
+ }
+
+ // Override this in the child class.
+ virtual void parse() = 0;
+ virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) = 0;
+ virtual bool hasData(StyleLayer const& layer_desc) const = 0;
+
+
+public:
+ const Tile::ID id;
+ const std::string name;
+ std::atomic<State> state;
+
+public:
+ const SourceInfo& source;
+
+protected:
+ std::unique_ptr<Request> req;
+ std::string data;
+
+ // Contains the tile ID string for painting debug information.
+ DebugFontBuffer debugFontBuffer;
+
+public:
+ DebugBucket debugBucket;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/tile_parser.cpp b/src/mbgl/map/tile_parser.cpp
new file mode 100644
index 0000000000..1e12e5fc16
--- /dev/null
+++ b/src/mbgl/map/tile_parser.cpp
@@ -0,0 +1,174 @@
+#include <mbgl/map/tile_parser.hpp>
+#include <mbgl/map/vector_tile_data.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/style/style_layer_group.hpp>
+#include <mbgl/renderer/fill_bucket.hpp>
+#include <mbgl/renderer/line_bucket.hpp>
+#include <mbgl/renderer/symbol_bucket.hpp>
+#include <mbgl/renderer/raster_bucket.hpp>
+#include <mbgl/util/raster.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/token.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/text/glyph_store.hpp>
+#include <mbgl/text/collision.hpp>
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/map/map.hpp>
+
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/utf.hpp>
+
+#include <locale>
+
+namespace mbgl {
+
+// Note: This destructor is seemingly empty, but we need to declare it anyway
+// because this object has a std::unique_ptr<> of a forward-declare type in
+// its header file.
+TileParser::~TileParser() = default;
+
+TileParser::TileParser(const std::string &data, VectorTileData &tile_,
+ const util::ptr<const Style> &style_,
+ GlyphAtlas & glyphAtlas_,
+ GlyphStore & glyphStore_,
+ SpriteAtlas & spriteAtlas_,
+ const util::ptr<Sprite> &sprite_,
+ TexturePool& texturePool_)
+ : vector_data(pbf((const uint8_t *)data.data(), data.size())),
+ tile(tile_),
+ style(style_),
+ glyphAtlas(glyphAtlas_),
+ glyphStore(glyphStore_),
+ spriteAtlas(spriteAtlas_),
+ sprite(sprite_),
+ texturePool(texturePool_),
+ collision(util::make_unique<Collision>(tile.id.z, 4096, tile.source.tile_size, tile.depth)) {
+ assert(&tile != nullptr);
+ assert(style);
+ assert(sprite);
+ assert(collision);
+}
+
+void TileParser::parse() {
+ parseStyleLayers(style->layers);
+}
+
+bool TileParser::obsolete() const { return tile.state == TileData::State::obsolete; }
+
+void TileParser::parseStyleLayers(util::ptr<StyleLayerGroup> group) {
+ if (!group) {
+ return;
+ }
+
+ for (const util::ptr<StyleLayer> &layer_desc : group->layers) {
+ // Cancel early when parsing.
+ if (obsolete()) {
+ return;
+ }
+
+ if (layer_desc->isBackground()) {
+ // background is a special, fake bucket
+ continue;
+ } else if (layer_desc->layers) {
+ // This is a layer group.
+ parseStyleLayers(layer_desc->layers);
+ }
+ if (layer_desc->bucket) {
+ // This is a singular layer. Check if this bucket already exists. If not,
+ // parse this bucket.
+ auto bucket_it = tile.buckets.find(layer_desc->bucket->name);
+ if (bucket_it == tile.buckets.end()) {
+ // We need to create this bucket since it doesn't exist yet.
+ std::unique_ptr<Bucket> bucket = createBucket(layer_desc->bucket);
+ if (bucket) {
+ // Bucket creation might fail because the data tile may not
+ // contain any data that falls into this bucket.
+ tile.buckets[layer_desc->bucket->name] = std::move(bucket);
+ }
+ }
+ } else {
+ fprintf(stderr, "[WARNING] layer '%s' does not have child layers or buckets\n", layer_desc->id.c_str());
+ }
+ }
+}
+
+std::unique_ptr<Bucket> TileParser::createBucket(util::ptr<StyleBucket> bucket_desc) {
+ if (!bucket_desc) {
+ fprintf(stderr, "missing bucket desc\n");
+ return nullptr;
+ }
+
+ // Skip this bucket if we are to not render this
+ if (tile.id.z < std::floor(bucket_desc->min_zoom) && std::floor(bucket_desc->min_zoom) < tile.source.max_zoom) return nullptr;
+ if (tile.id.z >= std::ceil(bucket_desc->max_zoom)) return nullptr;
+
+ auto layer_it = vector_data.layers.find(bucket_desc->source_layer);
+ if (layer_it != vector_data.layers.end()) {
+ const VectorTileLayer &layer = layer_it->second;
+ if (bucket_desc->render.is<StyleBucketFill>()) {
+ return createFillBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketFill>());
+ } else if (bucket_desc->render.is<StyleBucketLine>()) {
+ return createLineBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketLine>());
+ } else if (bucket_desc->render.is<StyleBucketSymbol>()) {
+ return createSymbolBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketSymbol>());
+ } else if (bucket_desc->render.is<StyleBucketRaster>()) {
+ return nullptr;
+ } else {
+ fprintf(stderr, "[WARNING] unknown bucket render type for layer '%s' (source layer '%s')\n", bucket_desc->name.c_str(), bucket_desc->source_layer.c_str());
+ }
+ } else if (bucket_desc->render.is<StyleBucketRaster>() && bucket_desc->render.get<StyleBucketRaster>().prerendered == true) {
+ return createRasterBucket(bucket_desc->render.get<StyleBucketRaster>());
+ } else {
+ // The layer specified in the bucket does not exist. Do nothing.
+ if (debug::tileParseWarnings) {
+ fprintf(stderr, "[WARNING] layer '%s' does not exist in tile %d/%d/%d\n",
+ bucket_desc->source_layer.c_str(), tile.id.z, tile.id.x, tile.id.y);
+ }
+ }
+
+ return nullptr;
+}
+
+template <class Bucket>
+void TileParser::addBucketGeometries(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter) {
+ FilteredVectorTileLayer filtered_layer(layer, filter);
+ for (pbf feature : filtered_layer) {
+ if (obsolete())
+ return;
+
+ while (feature.next(4)) { // geometry
+ pbf geometry_pbf = feature.message();
+ if (geometry_pbf) {
+ bucket->addGeometry(geometry_pbf);
+ } else if (debug::tileParseWarnings) {
+ fprintf(stderr, "[WARNING] geometry is empty\n");
+ }
+ }
+ }
+}
+
+std::unique_ptr<Bucket> TileParser::createFillBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketFill &fill) {
+ std::unique_ptr<FillBucket> bucket = util::make_unique<FillBucket>(tile.fillVertexBuffer, tile.triangleElementsBuffer, tile.lineElementsBuffer, fill);
+ addBucketGeometries(bucket, layer, filter);
+ return obsolete() ? nullptr : std::move(bucket);
+}
+
+std::unique_ptr<Bucket> TileParser::createRasterBucket(const StyleBucketRaster &raster) {
+ std::unique_ptr<RasterBucket> bucket = util::make_unique<RasterBucket>(texturePool, raster);
+ return obsolete() ? nullptr : std::move(bucket);
+}
+
+std::unique_ptr<Bucket> TileParser::createLineBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketLine &line) {
+ std::unique_ptr<LineBucket> bucket = util::make_unique<LineBucket>(tile.lineVertexBuffer, tile.triangleElementsBuffer, tile.pointElementsBuffer, line);
+ addBucketGeometries(bucket, layer, filter);
+ return obsolete() ? nullptr : std::move(bucket);
+}
+
+std::unique_ptr<Bucket> TileParser::createSymbolBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketSymbol &symbol) {
+ std::unique_ptr<SymbolBucket> bucket = util::make_unique<SymbolBucket>(symbol, *collision);
+ bucket->addFeatures(layer, filter, tile.id, spriteAtlas, *sprite, glyphAtlas, glyphStore);
+ return obsolete() ? nullptr : std::move(bucket);
+}
+
+}
diff --git a/src/mbgl/map/tile_parser.hpp b/src/mbgl/map/tile_parser.hpp
new file mode 100644
index 0000000000..beae3af831
--- /dev/null
+++ b/src/mbgl/map/tile_parser.hpp
@@ -0,0 +1,77 @@
+#ifndef MBGL_MAP_TILE_PARSER
+#define MBGL_MAP_TILE_PARSER
+
+#include <mbgl/map/vector_tile.hpp>
+#include <mbgl/style/filter_expression.hpp>
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/util/ptr.hpp>
+#include <mbgl/util/noncopyable.hpp>
+#include <cstdint>
+#include <iosfwd>
+#include <string>
+
+namespace mbgl {
+
+class Bucket;
+class TexturePool;
+class FontStack;
+class GlyphAtlas;
+class GlyphStore;
+class SpriteAtlas;
+class Sprite;
+class Style;
+class StyleBucket;
+class StyleBucketFill;
+class StyleBucketRaster;
+class StyleBucketLine;
+class StyleBucketSymbol;
+class StyleLayerGroup;
+class VectorTileData;
+class Collision;
+class TexturePool;
+
+class TileParser : private util::noncopyable
+{
+public:
+ TileParser(const std::string &data, VectorTileData &tile,
+ const util::ptr<const Style> &style,
+ GlyphAtlas & glyphAtlas,
+ GlyphStore & glyphStore,
+ SpriteAtlas & spriteAtlas,
+ const util::ptr<Sprite> &sprite,
+ TexturePool& texturePool);
+ ~TileParser();
+
+public:
+ void parse();
+
+private:
+ bool obsolete() const;
+ void parseStyleLayers(util::ptr<StyleLayerGroup> group);
+ std::unique_ptr<Bucket> createBucket(util::ptr<StyleBucket> bucket_desc);
+
+ std::unique_ptr<Bucket> createFillBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketFill &fill);
+ std::unique_ptr<Bucket> createRasterBucket(const StyleBucketRaster &raster);
+ std::unique_ptr<Bucket> createLineBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketLine &line);
+ std::unique_ptr<Bucket> createSymbolBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketSymbol &symbol);
+
+ template <class Bucket> void addBucketGeometries(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter);
+
+private:
+ const VectorTile vector_data;
+ VectorTileData& tile;
+
+ // Cross-thread shared data.
+ util::ptr<const Style> style;
+ GlyphAtlas & glyphAtlas;
+ GlyphStore & glyphStore;
+ SpriteAtlas & spriteAtlas;
+ util::ptr<Sprite> sprite;
+ TexturePool& texturePool;
+
+ std::unique_ptr<Collision> collision;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp
new file mode 100644
index 0000000000..d05d1f7446
--- /dev/null
+++ b/src/mbgl/map/transform.cpp
@@ -0,0 +1,472 @@
+#include <mbgl/map/transform.hpp>
+#include <mbgl/map/view.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/transition.hpp>
+#include <mbgl/platform/platform.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+const double D2R = M_PI / 180.0;
+const double M2PI = 2 * M_PI;
+
+Transform::Transform(View &view_)
+ : view(view_)
+{
+}
+
+#pragma mark - Map View
+
+bool Transform::resize(const uint16_t w, const uint16_t h, const float ratio,
+ const uint16_t fb_w, const uint16_t fb_h) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ if (final.width != w || final.height != h || final.pixelRatio != ratio ||
+ final.framebuffer[0] != fb_w || final.framebuffer[1] != fb_h) {
+
+ view.notify_map_change(MapChangeRegionWillChange);
+
+ current.width = final.width = w;
+ current.height = final.height = h;
+ current.pixelRatio = final.pixelRatio = ratio;
+ current.framebuffer[0] = final.framebuffer[0] = fb_w;
+ current.framebuffer[1] = final.framebuffer[1] = fb_h;
+ constrain(current.scale, current.y);
+
+ view.notify_map_change(MapChangeRegionDidChange);
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+#pragma mark - Position
+
+void Transform::moveBy(const double dx, const double dy, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _moveBy(dx, dy, duration);
+}
+
+void Transform::_moveBy(const double dx, const double dy, const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ view.notify_map_change(duration ?
+ MapChangeRegionWillChangeAnimated :
+ MapChangeRegionWillChange);
+
+ final.x = current.x + std::cos(current.angle) * dx + std::sin(current.angle) * dy;
+ final.y = current.y + std::cos(current.angle) * dy + std::sin(-current.angle) * dx;
+
+ constrain(final.scale, final.y);
+
+ if (duration == 0) {
+ current.x = final.x;
+ current.y = final.y;
+ } else {
+ // Use a common start time for all of the transitions to avoid divergent transitions.
+ timestamp start = util::now();
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration));
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration));
+ }
+
+ view.notify_map_change(duration ?
+ MapChangeRegionDidChangeAnimated :
+ MapChangeRegionDidChange,
+ duration);
+}
+
+void Transform::setLonLat(const double lon, const double lat, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ const double f = std::fmin(std::fmax(std::sin(D2R * lat), -0.9999), 0.9999);
+ double xn = -lon * Bc;
+ double yn = 0.5 * Cc * std::log((1 + f) / (1 - f));
+
+ _setScaleXY(current.scale, xn, yn, duration);
+}
+
+void Transform::setLonLatZoom(const double lon, const double lat, const double zoom,
+ const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ double new_scale = std::pow(2.0, zoom);
+
+ const double s = new_scale * util::tileSize;
+ Bc = s / 360;
+ Cc = s / (2 * M_PI);
+
+ const double f = std::fmin(std::fmax(std::sin(D2R * lat), -0.9999), 0.9999);
+ double xn = -lon * Bc;
+ double yn = 0.5 * Cc * log((1 + f) / (1 - f));
+
+ _setScaleXY(new_scale, xn, yn, duration);
+}
+
+void Transform::getLonLat(double &lon, double &lat) const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ final.getLonLat(lon, lat);
+}
+
+void Transform::getLonLatZoom(double &lon, double &lat, double &zoom) const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ getLonLat(lon, lat);
+ zoom = getZoom();
+}
+
+void Transform::startPanning() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearPanning();
+
+ // Add a 200ms timeout for resetting this to false
+ current.panning = true;
+ timestamp start = util::now();
+ pan_timeout = std::make_shared<util::timeout<bool>>(false, current.panning, start, 200_milliseconds);
+ transitions.emplace_front(pan_timeout);
+}
+
+void Transform::stopPanning() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearPanning();
+}
+
+void Transform::_clearPanning() {
+ current.panning = false;
+ if (pan_timeout) {
+ transitions.remove(pan_timeout);
+ pan_timeout.reset();
+ }
+}
+
+#pragma mark - Zoom
+
+void Transform::scaleBy(const double ds, const double cx, const double cy, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ // clamp scale to min/max values
+ double new_scale = current.scale * ds;
+ if (new_scale < min_scale) {
+ new_scale = min_scale;
+ } else if (new_scale > max_scale) {
+ new_scale = max_scale;
+ }
+
+ _setScale(new_scale, cx, cy, duration);
+}
+
+void Transform::setScale(const double scale, const double cx, const double cy,
+ const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _setScale(scale, cx, cy, duration);
+}
+
+void Transform::setZoom(const double zoom, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _setScale(std::pow(2.0, zoom), -1, -1, duration);
+}
+
+double Transform::getZoom() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return std::log(final.scale) / M_LN2;
+}
+
+double Transform::getScale() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return final.scale;
+}
+
+void Transform::startScaling() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearScaling();
+
+ // Add a 200ms timeout for resetting this to false
+ current.scaling = true;
+ timestamp start = util::now();
+ scale_timeout = std::make_shared<util::timeout<bool>>(false, current.scaling, start, 200_milliseconds);
+ transitions.emplace_front(scale_timeout);
+}
+
+void Transform::stopScaling() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearScaling();
+}
+
+double Transform::getMinZoom() const {
+ double test_scale = current.scale;
+ double test_y = current.y;
+ constrain(test_scale, test_y);
+
+ return std::log2(std::fmin(min_scale, test_scale));
+}
+
+double Transform::getMaxZoom() const {
+ return std::log2(max_scale);
+}
+
+void Transform::_clearScaling() {
+ // This is only called internally, so we don't need a lock here.
+
+ current.scaling = false;
+ if (scale_timeout) {
+ transitions.remove(scale_timeout);
+ scale_timeout.reset();
+ }
+}
+
+void Transform::_setScale(double new_scale, double cx, double cy, const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ // Ensure that we don't zoom in further than the maximum allowed.
+ if (new_scale < min_scale) {
+ new_scale = min_scale;
+ } else if (new_scale > max_scale) {
+ new_scale = max_scale;
+ }
+
+ // Zoom in on the center if we don't have click or gesture anchor coordinates.
+ if (cx < 0 || cy < 0) {
+ cx = current.width / 2;
+ cy = current.height / 2;
+ }
+
+ // Account for the x/y offset from the center (= where the user clicked or pinched)
+ const double factor = new_scale / current.scale;
+ const double dx = (cx - current.width / 2) * (1.0 - factor);
+ const double dy = (cy - current.height / 2) * (1.0 - factor);
+
+ // Account for angle
+ const double angle_sin = std::sin(-current.angle);
+ const double angle_cos = std::cos(-current.angle);
+ const double ax = angle_cos * dx - angle_sin * dy;
+ const double ay = angle_sin * dx + angle_cos * dy;
+
+ const double xn = current.x * factor + ax;
+ const double yn = current.y * factor + ay;
+
+ _setScaleXY(new_scale, xn, yn, duration);
+}
+
+void Transform::_setScaleXY(const double new_scale, const double xn, const double yn,
+ const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ view.notify_map_change(duration ?
+ MapChangeRegionWillChangeAnimated :
+ MapChangeRegionWillChange);
+
+ final.scale = new_scale;
+ final.x = xn;
+ final.y = yn;
+
+ constrain(final.scale, final.y);
+
+ if (duration == 0) {
+ current.scale = final.scale;
+ current.x = final.x;
+ current.y = final.y;
+ } else {
+ // Use a common start time for all of the transitions to avoid divergent transitions.
+ timestamp start = util::now();
+ transitions.emplace_front(std::make_shared<util::ease_transition<double>>(
+ current.scale, final.scale, current.scale, start, duration));
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration));
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration));
+ }
+
+ const double s = final.scale * util::tileSize;
+ Bc = s / 360;
+ Cc = s / (2 * M_PI);
+
+ view.notify_map_change(duration ?
+ MapChangeRegionDidChangeAnimated :
+ MapChangeRegionDidChange,
+ duration);
+}
+
+#pragma mark - Constraints
+
+void Transform::constrain(double& scale, double& y) const {
+ // Constrain minimum zoom to avoid zooming out far enough to show off-world areas.
+ if (scale < (current.height / util::tileSize)) scale = (current.height / util::tileSize);
+
+ // Constrain min/max vertical pan to avoid showing off-world areas.
+ double max_y = ((scale * util::tileSize) - current.height) / 2;
+
+ if (y > max_y) y = max_y;
+ if (y < -max_y) y = -max_y;
+}
+
+#pragma mark - Angle
+
+void Transform::rotateBy(const double start_x, const double start_y, const double end_x,
+ const double end_y, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ double center_x = current.width / 2, center_y = current.height / 2;
+
+ const double begin_center_x = start_x - center_x;
+ const double begin_center_y = start_y - center_y;
+
+ const double beginning_center_dist =
+ std::sqrt(begin_center_x * begin_center_x + begin_center_y * begin_center_y);
+
+ // If the first click was too close to the center, move the center of rotation by 200 pixels
+ // in the direction of the click.
+ if (beginning_center_dist < 200) {
+ const double offset_x = -200, offset_y = 0;
+ const double rotate_angle = std::atan2(begin_center_y, begin_center_x);
+ const double rotate_angle_sin = std::sin(rotate_angle);
+ const double rotate_angle_cos = std::cos(rotate_angle);
+ center_x = start_x + rotate_angle_cos * offset_x - rotate_angle_sin * offset_y;
+ center_y = start_y + rotate_angle_sin * offset_x + rotate_angle_cos * offset_y;
+ }
+
+ const double first_x = start_x - center_x, first_y = start_y - center_y;
+ const double second_x = end_x - center_x, second_y = end_y - center_y;
+
+ const double ang = current.angle + util::angle_between(first_x, first_y, second_x, second_y);
+
+ _setAngle(ang, duration);
+}
+
+void Transform::setAngle(const double new_angle, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _setAngle(new_angle, duration);
+}
+
+void Transform::setAngle(const double new_angle, const double cx, const double cy) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ double dx = 0, dy = 0;
+
+ if (cx >= 0 && cy >= 0) {
+ dx = (final.width / 2) - cx;
+ dy = (final.height / 2) - cy;
+ _moveBy(dx, dy, 0);
+ }
+
+ _setAngle(new_angle, 0);
+
+ if (cx >= 0 && cy >= 0) {
+ _moveBy(-dx, -dy, 0);
+ }
+}
+
+void Transform::_setAngle(double new_angle, const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ view.notify_map_change(duration ?
+ MapChangeRegionWillChangeAnimated :
+ MapChangeRegionWillChange);
+
+ while (new_angle > M_PI)
+ new_angle -= M2PI;
+ while (new_angle <= -M_PI)
+ new_angle += M2PI;
+
+ final.angle = new_angle;
+
+ if (duration == 0) {
+ current.angle = final.angle;
+ } else {
+ timestamp start = util::now();
+ transitions.emplace_front(std::make_shared<util::ease_transition<double>>(
+ current.angle, final.angle, current.angle, start, duration));
+ }
+
+ view.notify_map_change(duration ?
+ MapChangeRegionDidChangeAnimated :
+ MapChangeRegionDidChange,
+ duration);
+}
+
+double Transform::getAngle() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return final.angle;
+}
+
+void Transform::startRotating() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearRotating();
+
+ // Add a 200ms timeout for resetting this to false
+ current.rotating = true;
+ timestamp start = util::now();
+ rotate_timeout = std::make_shared<util::timeout<bool>>(false, current.rotating, start, 200_milliseconds);
+ transitions.emplace_front(rotate_timeout);
+}
+
+void Transform::stopRotating() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearRotating();
+}
+
+void Transform::_clearRotating() {
+ // This is only called internally, so we don't need a lock here.
+
+ current.rotating = false;
+ if (rotate_timeout) {
+ transitions.remove(rotate_timeout);
+ rotate_timeout.reset();
+ }
+}
+
+#pragma mark - Transition
+
+bool Transform::needsTransition() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return !transitions.empty();
+}
+
+void Transform::updateTransitions(const timestamp now) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ transitions.remove_if([now](const util::ptr<util::transition> &transition) {
+ return transition->update(now) == util::transition::complete;
+ });
+}
+
+void Transform::cancelTransitions() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ transitions.clear();
+}
+
+#pragma mark - Transform state
+
+const TransformState Transform::currentState() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return current;
+}
+
+const TransformState Transform::finalState() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return final;
+}
diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp
new file mode 100644
index 0000000000..a7da8ccab2
--- /dev/null
+++ b/src/mbgl/map/transform_state.cpp
@@ -0,0 +1,172 @@
+#include <mbgl/map/transform_state.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/box.hpp>
+
+using namespace mbgl;
+
+const double R2D = 180.0 / M_PI;
+
+#pragma mark - Matrix
+
+void TransformState::matrixFor(mat4& matrix, const Tile::ID& id) const {
+ const double tile_scale = std::pow(2, id.z);
+ const double tile_size = scale * util::tileSize / tile_scale;
+
+ matrix::identity(matrix);
+
+ matrix::translate(matrix, matrix, 0.5f * (float)width, 0.5f * (float)height, 0);
+ matrix::rotate_z(matrix, matrix, angle);
+ matrix::translate(matrix, matrix, -0.5f * (float)width, -0.5f * (float)height, 0);
+
+ matrix::translate(matrix, matrix, pixel_x() + id.x * tile_size, pixel_y() + id.y * tile_size, 0);
+
+ // TODO: Get rid of the 8 (scaling from 4096 to tile size);
+ float factor = scale / tile_scale / (4096.0f / util::tileSize);
+ matrix::scale(matrix, matrix, factor, factor, 1);
+}
+
+box TransformState::cornersToBox(uint32_t z) const {
+ const double ref_scale = std::pow(2, z);
+
+ const double angle_sin = std::sin(-angle);
+ const double angle_cos = std::cos(-angle);
+
+ const double w_2 = width / 2;
+ const double h_2 = height / 2;
+ const double ss_0 = scale * util::tileSize;
+ const double ss_1 = ref_scale / ss_0;
+ const double ss_2 = ss_0 / 2.0;
+
+ // Calculate the corners of the map view. The resulting coordinates will be
+ // in fractional tile coordinates.
+ box b;
+
+ b.tl.x = ((-w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
+ b.tl.y = ((-w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
+
+ b.tr.x = ((+w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
+ b.tr.y = ((+w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
+
+ b.bl.x = ((-w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
+ b.bl.y = ((-w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
+
+ b.br.x = ((+w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
+ b.br.y = ((+w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
+
+ b.center.x = (ss_2 - x) * ss_1;
+ b.center.y = (ss_2 - y) * ss_1;
+
+ return b;
+}
+
+
+#pragma mark - Dimensions
+
+bool TransformState::hasSize() const {
+ return width && height;
+}
+
+uint16_t TransformState::getWidth() const {
+ return width;
+}
+
+uint16_t TransformState::getHeight() const {
+ return height;
+}
+
+uint16_t TransformState::getFramebufferWidth() const {
+ return framebuffer[0];
+}
+
+uint16_t TransformState::getFramebufferHeight() const {
+ return framebuffer[1];
+}
+
+const std::array<uint16_t, 2> TransformState::getFramebufferDimensions() const {
+ return framebuffer;
+}
+
+float TransformState::getPixelRatio() const {
+ return pixelRatio;
+}
+
+float TransformState::worldSize() const {
+ return scale * util::tileSize;
+}
+
+float TransformState::lngX(float lon) const {
+ return (180 + lon) * worldSize() / 360;
+}
+
+float TransformState::latY(float lat) const {
+ float lat_y = 180 / M_PI * std::log(std::tan(M_PI / 4 + lat * M_PI / 360));
+ return (180 - lat_y) * worldSize() / 360;
+}
+
+std::array<float, 2> TransformState::locationCoordinate(float lon, float lat) const {
+ float k = std::pow(2, getIntegerZoom()) / worldSize();
+ return {{
+ lngX(lon) * k,
+ latY(lat) * k
+ }};
+}
+
+void TransformState::getLonLat(double &lon, double &lat) const {
+ const double s = scale * util::tileSize;
+ const double Bc = s / 360;
+ const double Cc = s / (2 * M_PI);
+
+ lon = -x / Bc;
+ lat = R2D * (2 * std::atan(std::exp(y / Cc)) - 0.5 * M_PI);
+}
+
+
+#pragma mark - Zoom
+
+float TransformState::getNormalizedZoom() const {
+ return std::log(scale * util::tileSize / 512.0f) / M_LN2;
+}
+
+double TransformState::getZoom() const {
+ return std::log(scale) / M_LN2;
+}
+
+int32_t TransformState::getIntegerZoom() const {
+ return std::floor(getZoom());
+}
+
+double TransformState::getZoomFraction() const {
+ return getZoom() - getIntegerZoom();
+}
+
+double TransformState::getScale() const {
+ return scale;
+}
+
+
+#pragma mark - Rotation
+
+float TransformState::getAngle() const {
+ return angle;
+}
+
+
+#pragma mark - Changing
+
+bool TransformState::isChanging() const {
+ return rotating || scaling || panning;
+}
+
+
+#pragma mark - (private helper functions)
+
+
+double TransformState::pixel_x() const {
+ const double center = (width - scale * util::tileSize) / 2;
+ return center + x;
+}
+
+double TransformState::pixel_y() const {
+ const double center = (height - scale * util::tileSize) / 2;
+ return center + y;
+}
diff --git a/src/mbgl/map/vector_tile.cpp b/src/mbgl/map/vector_tile.cpp
new file mode 100644
index 0000000000..ac7134fb0c
--- /dev/null
+++ b/src/mbgl/map/vector_tile.cpp
@@ -0,0 +1,214 @@
+#include <mbgl/map/vector_tile.hpp>
+#include <mbgl/style/filter_expression_private.hpp>
+
+#include <algorithm>
+#include <iostream>
+
+using namespace mbgl;
+
+
+std::ostream& mbgl::operator<<(std::ostream& os, const FeatureType& type) {
+ switch (type) {
+ case FeatureType::Unknown: return os << "Unknown";
+ case FeatureType::Point: return os << "Point";
+ case FeatureType::LineString: return os << "LineString";
+ case FeatureType::Polygon: return os << "Polygon";
+ default: return os << "Invalid";
+ }
+}
+
+VectorTileFeature::VectorTileFeature(pbf feature, const VectorTileLayer& layer) {
+ while (feature.next()) {
+ if (feature.tag == 1) { // id
+ id = feature.varint<uint64_t>();
+ } else if (feature.tag == 2) { // tags
+ // tags are packed varints. They should have an even length.
+ pbf tags = feature.message();
+ while (tags) {
+ uint32_t tag_key = tags.varint();
+
+ if (layer.keys.size() <= tag_key) {
+ throw std::runtime_error("feature referenced out of range key");
+ }
+
+ if (tags) {
+ uint32_t tag_val = tags.varint();
+ if (layer.values.size() <= tag_val) {
+ throw std::runtime_error("feature referenced out of range value");
+ }
+
+ properties.emplace(layer.keys[tag_key], layer.values[tag_val]);
+ } else {
+ throw std::runtime_error("uneven number of feature tag ids");
+ }
+ }
+ } else if (feature.tag == 3) { // type
+ type = (FeatureType)feature.varint();
+ } else if (feature.tag == 4) { // geometry
+ geometry = feature.message();
+ } else {
+ feature.skip();
+ }
+ }
+}
+
+
+std::ostream& mbgl::operator<<(std::ostream& os, const VectorTileFeature& feature) {
+ os << "Feature(" << feature.id << "): " << feature.type << std::endl;
+ for (const auto& prop : feature.properties) {
+ os << " - " << prop.first << ": " << prop.second << std::endl;
+ }
+ return os;
+}
+
+
+VectorTile::VectorTile() {}
+
+
+VectorTile::VectorTile(pbf tile) {
+ while (tile.next()) {
+ if (tile.tag == 3) { // layer
+ VectorTileLayer layer(tile.message());
+ layers.emplace(layer.name, std::forward<VectorTileLayer>(layer));
+ } else {
+ tile.skip();
+ }
+ }
+}
+
+VectorTile& VectorTile::operator=(VectorTile && other) {
+ if (this != &other) {
+ layers.swap(other.layers);
+ }
+ return *this;
+}
+
+VectorTileLayer::VectorTileLayer(pbf layer) : data(layer) {
+ std::vector<std::string> stacks;
+
+ while (layer.next()) {
+ if (layer.tag == 1) { // name
+ name = layer.string();
+ } else if (layer.tag == 3) { // keys
+ keys.emplace_back(layer.string());
+ key_index.emplace(keys.back(), keys.size() - 1);
+ } else if (layer.tag == 4) { // values
+ values.emplace_back(std::move(parseValue(layer.message())));
+ } else if (layer.tag == 5) { // extent
+ extent = layer.varint();
+ } else {
+ layer.skip();
+ }
+ }
+}
+
+FilteredVectorTileLayer::FilteredVectorTileLayer(const VectorTileLayer& layer_, const FilterExpression &filterExpression_)
+ : layer(layer_),
+ filterExpression(filterExpression_) {
+}
+
+FilteredVectorTileLayer::iterator FilteredVectorTileLayer::begin() const {
+ return iterator(*this, layer.data);
+}
+
+FilteredVectorTileLayer::iterator FilteredVectorTileLayer::end() const {
+ return iterator(*this, pbf(layer.data.end, 0));
+}
+
+FilteredVectorTileLayer::iterator::iterator(const FilteredVectorTileLayer& parent_, const pbf& data_)
+ : parent(parent_),
+ feature(pbf()),
+ data(data_) {
+ operator++();
+}
+
+VectorTileTagExtractor::VectorTileTagExtractor(const VectorTileLayer &layer) : layer_(layer) {}
+
+
+void VectorTileTagExtractor::setTags(const pbf &pbf) {
+ tags_ = pbf;
+}
+
+mapbox::util::optional<Value> VectorTileTagExtractor::getValue(const std::string &key) const {
+ if (key == "$type") {
+ return Value(uint64_t(type_));
+ }
+
+ mapbox::util::optional<Value> value;
+
+ auto field_it = layer_.key_index.find(key);
+ if (field_it != layer_.key_index.end()) {
+ const uint32_t filter_key = field_it->second;
+
+ // Now loop through all the key/value pair tags.
+ // tags are packed varints. They should have an even length.
+ pbf tags_pbf = tags_;
+ uint32_t tag_key, tag_val;
+ while (tags_pbf) {
+ tag_key = tags_pbf.varint();
+ if (!tags_pbf) {
+ // This should not happen; otherwise the vector tile is invalid.
+ fprintf(stderr, "[WARNING] uneven number of feature tag ids\n");
+ return value;
+ }
+ // Note: We need to run this command in all cases, even if the keys don't match.
+ tag_val = tags_pbf.varint();
+
+ if (tag_key == filter_key) {
+ if (layer_.values.size() > tag_val) {
+ value = layer_.values[tag_val];
+ } else {
+ fprintf(stderr, "[WARNING] feature references out of range value\n");
+ break;
+ }
+ }
+ }
+ }
+
+ return value;
+}
+
+void VectorTileTagExtractor::setType(FeatureType type) {
+ type_ = type;
+}
+
+template bool mbgl::evaluate(const FilterExpression&, const VectorTileTagExtractor&);
+
+void FilteredVectorTileLayer::iterator::operator++() {
+ valid = false;
+
+ const FilterExpression &expression = parent.filterExpression;
+
+ while (data.next(2)) { // feature
+ feature = data.message();
+ pbf feature_pbf = feature;
+
+ VectorTileTagExtractor extractor(parent.layer);
+
+ // Retrieve the basic information
+ while (feature_pbf.next()) {
+ if (feature_pbf.tag == 2) { // tags
+ extractor.setTags(feature_pbf.message());
+ } else if (feature_pbf.tag == 3) { // geometry type
+ extractor.setType(FeatureType(feature_pbf.varint()));
+ } else {
+ feature_pbf.skip();
+ }
+ }
+
+ if (evaluate(expression, extractor)) {
+ valid = true;
+ return; // data loop
+ } else {
+ valid = false;
+ }
+ }
+}
+
+bool FilteredVectorTileLayer::iterator::operator!=(const iterator& other) const {
+ return !(data.data == other.data.data && data.end == other.data.end && valid == other.valid);
+}
+
+const pbf& FilteredVectorTileLayer::iterator:: operator*() const {
+ return feature;
+}
diff --git a/src/mbgl/map/vector_tile.hpp b/src/mbgl/map/vector_tile.hpp
new file mode 100644
index 0000000000..2d02ba3a0b
--- /dev/null
+++ b/src/mbgl/map/vector_tile.hpp
@@ -0,0 +1,118 @@
+#ifndef MBGL_MAP_VECTOR_TILE
+#define MBGL_MAP_VECTOR_TILE
+
+#include <mbgl/style/filter_expression.hpp>
+#include <mbgl/style/value.hpp>
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/util/pbf.hpp>
+#include <mbgl/util/optional.hpp>
+
+#include <cstdint>
+#include <iosfwd>
+#include <map>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace mbgl {
+
+class VectorTileLayer;
+
+enum class FeatureType {
+ Unknown = 0,
+ Point = 1,
+ LineString = 2,
+ Polygon = 3
+};
+
+std::ostream& operator<<(std::ostream&, const FeatureType& type);
+
+class VectorTileFeature {
+public:
+ VectorTileFeature(pbf feature, const VectorTileLayer& layer);
+
+ uint64_t id = 0;
+ FeatureType type = FeatureType::Unknown;
+ std::map<std::string, Value> properties;
+ pbf geometry;
+};
+
+std::ostream& operator<<(std::ostream&, const VectorTileFeature& feature);
+
+
+class VectorTileTagExtractor {
+public:
+ VectorTileTagExtractor(const VectorTileLayer &layer);
+
+ void setTags(const pbf &pbf);
+ mapbox::util::optional<Value> getValue(const std::string &key) const;
+ void setType(FeatureType type);
+ FeatureType getType() const;
+
+private:
+ const VectorTileLayer &layer_;
+ pbf tags_;
+ FeatureType type_ = FeatureType::Unknown;
+};
+
+/*
+ * Allows iterating over the features of a VectorTileLayer using a
+ * BucketDescription as filter. Only features matching the descriptions will
+ * be returned (as pbf).
+ */
+class FilteredVectorTileLayer {
+public:
+ class iterator {
+ public:
+ iterator(const FilteredVectorTileLayer& filter, const pbf& data);
+ void operator++();
+ bool operator!=(const iterator& other) const;
+ const pbf& operator*() const;
+
+ private:
+ const FilteredVectorTileLayer& parent;
+ bool valid = false;
+ pbf feature;
+ pbf data;
+ };
+
+public:
+ FilteredVectorTileLayer(const VectorTileLayer& layer, const FilterExpression &filterExpression);
+
+ iterator begin() const;
+ iterator end() const;
+
+private:
+ const VectorTileLayer& layer;
+ const FilterExpression& filterExpression;
+};
+
+std::ostream& operator<<(std::ostream&, const PositionedGlyph& placement);
+
+class VectorTileLayer {
+public:
+ VectorTileLayer(pbf data);
+
+ const pbf data;
+ std::string name;
+ uint32_t extent = 4096;
+ std::vector<std::string> keys;
+ std::unordered_map<std::string, uint32_t> key_index;
+ std::vector<Value> values;
+ std::map<std::string, std::map<Value, Shaping>> shaping;
+};
+
+class VectorTile {
+public:
+ VectorTile();
+ VectorTile(pbf data);
+ VectorTile& operator=(VectorTile&& other);
+
+ std::map<std::string, const VectorTileLayer> layers;
+};
+
+
+
+}
+
+#endif
diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp
new file mode 100644
index 0000000000..06782057f6
--- /dev/null
+++ b/src/mbgl/map/vector_tile_data.cpp
@@ -0,0 +1,78 @@
+#include <mbgl/map/vector_tile_data.hpp>
+#include <mbgl/map/tile_parser.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/style/style_bucket.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+
+using namespace mbgl;
+
+VectorTileData::VectorTileData(Tile::ID const& id_,
+ float mapMaxZoom, util::ptr<Style> style_,
+ GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_,
+ SpriteAtlas& spriteAtlas_, util::ptr<Sprite> sprite_,
+ TexturePool& texturePool_,
+ const SourceInfo& source_)
+ : TileData(id_, source_),
+ glyphAtlas(glyphAtlas_),
+ glyphStore(glyphStore_),
+ spriteAtlas(spriteAtlas_),
+ sprite(sprite_),
+ texturePool(texturePool_),
+ style(style_),
+ depth(id.z >= source.max_zoom ? mapMaxZoom - id.z : 1) {
+}
+
+VectorTileData::~VectorTileData() {
+ glyphAtlas.removeGlyphs(id.to_uint64());
+}
+
+
+void VectorTileData::parse() {
+ if (state != State::loaded) {
+ return;
+ }
+
+ try {
+ // Parsing creates state that is encapsulated in TileParser. While parsing,
+ // the TileParser object writes results into this objects. All other state
+ // is going to be discarded afterwards.
+ TileParser parser(data, *this, style,
+ glyphAtlas, glyphStore,
+ spriteAtlas, sprite,
+ texturePool);
+ parser.parse();
+ } catch (const std::exception& ex) {
+#if defined(DEBUG)
+ fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
+#endif
+ cancel();
+ return;
+ }
+
+ if (state != State::obsolete) {
+ state = State::parsed;
+ }
+}
+
+void VectorTileData::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) {
+ if (state == State::parsed && layer_desc->bucket) {
+ auto databucket_it = buckets.find(layer_desc->bucket->name);
+ if (databucket_it != buckets.end()) {
+ assert(databucket_it->second);
+ databucket_it->second->render(painter, layer_desc, id, matrix);
+ }
+ }
+}
+
+bool VectorTileData::hasData(StyleLayer const& layer_desc) const {
+ if (state == State::parsed && layer_desc.bucket) {
+ auto databucket_it = buckets.find(layer_desc.bucket->name);
+ if (databucket_it != buckets.end()) {
+ assert(databucket_it->second);
+ return databucket_it->second->hasData();
+ }
+ }
+ return false;
+}
diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp
new file mode 100644
index 0000000000..b9bf55a1b3
--- /dev/null
+++ b/src/mbgl/map/vector_tile_data.hpp
@@ -0,0 +1,74 @@
+#ifndef MBGL_MAP_VECTOR_TILE_DATA
+#define MBGL_MAP_VECTOR_TILE_DATA
+
+#include <mbgl/map/tile.hpp>
+#include <mbgl/map/tile_data.hpp>
+#include <mbgl/geometry/elements_buffer.hpp>
+#include <mbgl/geometry/fill_buffer.hpp>
+#include <mbgl/geometry/icon_buffer.hpp>
+#include <mbgl/geometry/line_buffer.hpp>
+#include <mbgl/geometry/text_buffer.hpp>
+
+#include <iosfwd>
+#include <memory>
+#include <unordered_map>
+
+namespace mbgl {
+
+class Bucket;
+class Painter;
+class SourceInfo;
+class StyleLayer;
+class TileParser;
+class GlyphAtlas;
+class GlyphStore;
+class SpriteAtlas;
+class Sprite;
+class TexturePool;
+class Style;
+
+class VectorTileData : public TileData {
+ friend class TileParser;
+
+public:
+ VectorTileData(Tile::ID const&,
+ float mapMaxZoom, util::ptr<Style>,
+ GlyphAtlas&, GlyphStore&,
+ SpriteAtlas&, util::ptr<Sprite>,
+ TexturePool&,
+ const SourceInfo&);
+ ~VectorTileData();
+
+ virtual void parse();
+ virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix);
+ virtual bool hasData(StyleLayer const& layer_desc) const;
+
+protected:
+ // Holds the actual geometries in this tile.
+ FillVertexBuffer fillVertexBuffer;
+ LineVertexBuffer lineVertexBuffer;
+ IconVertexBuffer iconVertexBuffer;
+ TextVertexBuffer textVertexBuffer;
+
+ TriangleElementsBuffer triangleElementsBuffer;
+ LineElementsBuffer lineElementsBuffer;
+ PointElementsBuffer pointElementsBuffer;
+
+ // Holds the buckets of this tile.
+ // They contain the location offsets in the buffers stored above
+ std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets;
+
+ GlyphAtlas& glyphAtlas;
+ GlyphStore& glyphStore;
+ SpriteAtlas& spriteAtlas;
+ util::ptr<Sprite> sprite;
+ TexturePool& texturePool;
+ util::ptr<Style> style;
+
+public:
+ const float depth;
+};
+
+}
+
+#endif