summaryrefslogtreecommitdiff
path: root/src/mbgl/map/map.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/map/map.cpp')
-rw-r--r--src/mbgl/map/map.cpp654
1 files changed, 654 insertions, 0 deletions
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
new file mode 100644
index 0000000000..5be29d7543
--- /dev/null
+++ b/src/mbgl/map/map.cpp
@@ -0,0 +1,654 @@
+#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/util/mapbox.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 <algorithm>
+#include <iostream>
+
+#define _USE_MATH_DEFINES
+#include <cmath>
+
+#include <uv.h>
+
+// 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)),
+ 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() {
+ 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.
+ glyphStore.reset();
+ style.reset();
+ workers.reset();
+ activeSources.clear();
+
+ // 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();
+ });
+
+ 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(stop_callback cb, void *data) {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(mainThread != mapThread);
+ assert(async);
+
+ asyncTerminate->send();
+
+ if (cb) {
+ // 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) {
+ cb(data);
+ }
+ }
+
+ // If a callback function was provided, this should return immediately because the thread has
+ // already finished executing.
+ thread.join();
+
+ async = false;
+}
+
+void Map::run() {
+#ifndef NDEBUG
+ if (!async) {
+ mapThread = mainThread;
+ }
+#endif
+ assert(std::this_thread::get_id() == mapThread);
+
+ setup();
+ prepare();
+ 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
+ }
+}
+
+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();
+}
+
+void Map::setReachability(bool reachable) {
+ // Note: This function may be called from *any* thread.
+ if (reachable) {
+ fileSource.prepare([&]() {
+ fileSource.retryAllPending();
+ });
+ }
+}
+
+#pragma mark - Setup
+
+void Map::setup() {
+ assert(std::this_thread::get_id() == mapThread);
+ assert(painter);
+ view.make_active();
+ painter->setup();
+ view.make_inactive();
+}
+
+void Map::setStyleURL(const std::string &url) {
+ // TODO: Make threadsafe.
+ styleURL = url;
+}
+
+
+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());
+ if (!fileSource.hasLoop()) {
+ fileSource.setLoop(**loop);
+ glyphStore = std::make_shared<GlyphStore>(fileSource);
+ }
+ fileSource.setBase(base);
+ glyphStore->setURL(util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken()));
+ update();
+}
+
+std::string Map::getStyleJSON() const {
+ return styleJSON;
+}
+
+void Map::setAccessToken(std::string accessToken_) {
+ // TODO: Make threadsafe.
+ accessToken.swap(accessToken_);
+}
+
+std::string Map::getAccessToken() const {
+ return accessToken;
+}
+
+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 fb_width, uint16_t fb_height) {
+ if (transform.resize(width, height, ratio, fb_width, fb_height)) {
+ 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) {
+ style->setAppliedClasses(classes);
+ if (style->hasTransitions()) {
+ update();
+ }
+}
+
+
+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) {
+ style->setDefaultTransitionDuration(milliseconds);
+}
+
+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);
+ glyphStore = std::make_shared<GlyphStore>(fileSource);
+ }
+
+ 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() {
+ view.make_active();
+
+ assert(painter);
+ painter->render(*style, activeSources,
+ state, animationTime);
+ // Schedule another rerender when we definitely need a next frame.
+ if (transform.needsTransition() || style->hasTransitions()) {
+ update();
+ }
+
+ view.make_inactive();
+}