diff options
author | Leith Bade <leith@mapbox.com> | 2014-12-05 23:16:49 +1100 |
---|---|---|
committer | Leith Bade <leith@mapbox.com> | 2014-12-05 23:16:49 +1100 |
commit | de9eb00276684a10f49a1c490f55266b80238155 (patch) | |
tree | fc713b06541bb1af04e95c70f2e383bcb86a164a /src/mbgl/map | |
parent | c348c141c5c5754c962d9b7e94af83f097e30487 (diff) | |
parent | ff640132de0fe855314a8fd86adae3a2fb33237b (diff) | |
download | qtlocation-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.cpp | 745 | ||||
-rw-r--r-- | src/mbgl/map/raster_tile_data.cpp | 34 | ||||
-rw-r--r-- | src/mbgl/map/raster_tile_data.hpp | 33 | ||||
-rw-r--r-- | src/mbgl/map/source.cpp | 369 | ||||
-rw-r--r-- | src/mbgl/map/source.hpp | 86 | ||||
-rw-r--r-- | src/mbgl/map/sprite.cpp | 152 | ||||
-rw-r--r-- | src/mbgl/map/sprite.hpp | 79 | ||||
-rw-r--r-- | src/mbgl/map/tile.cpp | 147 | ||||
-rw-r--r-- | src/mbgl/map/tile_data.cpp | 104 | ||||
-rw-r--r-- | src/mbgl/map/tile_data.hpp | 88 | ||||
-rw-r--r-- | src/mbgl/map/tile_parser.cpp | 174 | ||||
-rw-r--r-- | src/mbgl/map/tile_parser.hpp | 77 | ||||
-rw-r--r-- | src/mbgl/map/transform.cpp | 472 | ||||
-rw-r--r-- | src/mbgl/map/transform_state.cpp | 172 | ||||
-rw-r--r-- | src/mbgl/map/vector_tile.cpp | 214 | ||||
-rw-r--r-- | src/mbgl/map/vector_tile.hpp | 118 | ||||
-rw-r--r-- | src/mbgl/map/vector_tile_data.cpp | 78 | ||||
-rw-r--r-- | src/mbgl/map/vector_tile_data.hpp | 74 |
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([¢er](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 |