From 0168612ed311cf71802575aa3fb886edf2ca62fe Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Mon, 18 Aug 2014 17:49:50 -0700 Subject: [PATCH] Load TileJSON; support inline TileJSON sources Fixes #412 Refs https://github.com/mapbox/mapbox-gl-style-spec/pull/143 --- include/mbgl/map/source.hpp | 13 +++-- include/mbgl/map/tile_data.hpp | 3 -- include/mbgl/style/style_source.hpp | 31 +++++++----- src/map/map.cpp | 3 +- src/map/source.cpp | 67 ++++++++++++++++++------- src/map/tile_data.cpp | 23 +++++---- src/style/style_parser.cpp | 20 +++----- src/style/style_source.cpp | 77 +++++++++++++++++++++++++++++ 8 files changed, 176 insertions(+), 61 deletions(-) create mode 100644 src/style/style_source.cpp diff --git a/include/mbgl/map/source.hpp b/include/mbgl/map/source.hpp index 4ffd09719..5b51268bf 100644 --- a/include/mbgl/map/source.hpp +++ b/include/mbgl/map/source.hpp @@ -25,7 +25,9 @@ struct box; class Source : public std::enable_shared_from_this, private util::noncopyable { public: - Source(SourceInfo info, const std::string &access_token = ""); + Source(SourceInfo& info); + + void load(Map &map); bool update(Map &map); void updateMatrices(const mat4 &projMatrix, const TransformState &transform); @@ -38,11 +40,6 @@ public: std::forward_list getIDs() const; void updateClipIDs(const std::map &mapping); - static std::string normalizeSourceURL(const std::string &url, const std::string &access_token); - -public: - const SourceInfo info; - private: bool findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list& retain); bool findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list& retain); @@ -55,7 +52,9 @@ private: double getZoom(const TransformState &state) const; -private: + SourceInfo& info; + bool loaded = false; + // Stores the time when this source was most recently updated. timestamp updated = 0; diff --git a/include/mbgl/map/tile_data.hpp b/include/mbgl/map/tile_data.hpp index 5991613a2..3e639fcea 100644 --- a/include/mbgl/map/tile_data.hpp +++ b/include/mbgl/map/tile_data.hpp @@ -67,9 +67,6 @@ protected: public: const SourceInfo &source; - // Request-related information. - const std::string url; - protected: std::weak_ptr req; std::string data; diff --git a/include/mbgl/style/style_source.hpp b/include/mbgl/style/style_source.hpp index fa5c8dfb3..b598550c6 100644 --- a/include/mbgl/style/style_source.hpp +++ b/include/mbgl/style/style_source.hpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include namespace mbgl { @@ -11,27 +15,32 @@ class Source; class SourceInfo { public: - const SourceType type; - const std::string url; - const uint32_t tile_size; - const int32_t min_zoom; - const int32_t max_zoom; + SourceType type = SourceType::Vector; + std::string url; + std::vector tiles; + uint16_t tile_size = 512; + uint16_t min_zoom = 0; + uint16_t max_zoom = 22; + std::string attribution; + std::array center = {{0, 0, 0}}; + std::array bounds = {{-180, -90, 180, 90}}; - SourceInfo(SourceType type = SourceType::Vector, const std::string &url = "", - uint32_t tile_size = 512, int32_t min_zoom = 0, int32_t max_zoom = 22) - : type(type), url(url), tile_size(tile_size), min_zoom(min_zoom), max_zoom(max_zoom) {} + void parseTileJSONProperties(const rapidjson::Value&); }; -class StyleSource { +class StyleSource : public std::enable_shared_from_this { public: - const SourceInfo info; + SourceInfo info; bool enabled = false; std::shared_ptr source; - StyleSource(const SourceInfo &info) : info(info) {} + StyleSource(const SourceInfo &info) + : info(info) + {} }; + } #endif diff --git a/src/map/map.cpp b/src/map/map.cpp index 6aa9071a4..4fa483898 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -469,7 +469,8 @@ void Map::updateSources() { for (const std::shared_ptr &style_source : activeSources) { if (style_source->enabled) { if (!style_source->source) { - style_source->source = std::make_shared(style_source->info, getAccessToken()); + style_source->source = std::make_shared(style_source->info); + style_source->source->load(*this); } } else { style_source->source.reset(); diff --git a/src/map/source.cpp b/src/map/source.cpp index 523a7f3bc..db83cccc1 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -6,40 +6,73 @@ #include #include #include +#include #include #include #include #include - +#include #include #include namespace mbgl { -Source::Source(SourceInfo info, const std::string &access_token) - : info( - info.type, - normalizeSourceURL(info.url, access_token), - info.tile_size, - info.min_zoom, - info.max_zoom - ) {} +Source::Source(SourceInfo& info) + : info(info) +{ +} -std::string Source::normalizeSourceURL(const std::string &url, const std::string &access_token) { +std::string normalizeURL(const std::string& url, const std::string& access_token) { const std::string t = "mapbox://"; - if (url.compare(0, t.length(), t) == 0) { - if (access_token.empty()) { - throw std::runtime_error("You must provide a Mapbox API access token for Mapbox tile sources"); - } - return std::string("https://api.tiles.mapbox.com/v4/") + url.substr(t.length()) + "/{z}/{x}/{y}.vector.pbf?access_token=" + access_token; - } else { + + if (url.compare(0, t.length(), t) != 0) return url; + + if (access_token.empty()) + throw std::runtime_error("You must provide a Mapbox API access token for Mapbox tile sources"); + + return std::string("https://api.tiles.mapbox.com/v4/") + + url.substr(t.length()) + + ".json?access_token=" + + access_token; +} + +// 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) { + if (info.url.empty()) { + loaded = true; + return; } + + std::string url = normalizeURL(info.url, map.getAccessToken()); + std::shared_ptr source = shared_from_this(); + + map.getFileSource()->load(ResourceType::JSON, url, [source, &map](platform::Response *res) { + if (res->code != 200) { + Log::Warning(Event::General, "failed to load source TileJSON"); + return; + } + + rapidjson::Document d; + d.Parse<0>(res->body.c_str()); + + if (d.HasParseError()) { + Log::Warning(Event::General, "invalid source TileJSON"); + return; + } + + source->info.parseTileJSONProperties(d); + source->loaded = true; + + map.update(); + }); } bool Source::update(Map &map) { - if (map.getTime() > updated) { + if (loaded && map.getTime() > updated) { return updateTiles(map); } else { return false; diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp index 4333e2d2f..57afb8dad 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -14,13 +14,6 @@ TileData::TileData(Tile::ID id, Map &map, const SourceInfo &source) state(State::initial), map(map), source(source), - url(util::replaceTokens(source.url, [&](const std::string &token) -> std::string { - if (token == "z") return std::to_string(id.z); - if (token == "x") return std::to_string(id.x); - if (token == "y") return std::to_string(id.y); - if (token == "ratio") return (map.getState().getPixelRatio() > 1.0 ? "@2x" : ""); - return ""; - })), debugBucket(debugFontBuffer) { // Initialize tile debug coordinates const std::string str = util::sprintf<32>("%d/%d/%d", id.z, id.x, id.y); @@ -36,11 +29,23 @@ const std::string TileData::toString() const { } void TileData::request() { + 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 std::to_string(id.z); + if (token == "x") return std::to_string(id.x); + if (token == "y") return std::to_string(id.y); + if (token == "ratio") return (map.getState().getPixelRatio() > 1.0 ? "@2x" : ""); + return ""; + }); + state = State::loading; // Note: Somehow this feels slower than the change to request_http() std::weak_ptr weak_tile = shared_from_this(); - map.getFileSource()->load(ResourceType::Tile, url, [weak_tile](platform::Response *res) { + map.getFileSource()->load(ResourceType::Tile, url, [weak_tile, url](platform::Response *res) { std::shared_ptr tile = weak_tile.lock(); if (!tile || tile->state == State::obsolete) { // noop. Tile is obsolete and we're now just waiting for the refcount @@ -54,7 +59,7 @@ void TileData::request() { tile->reparse(); } else { #if defined(DEBUG) - fprintf(stderr, "[%s] tile loading failed: %d, %s\n", tile->url.c_str(), res->code, res->error_message.c_str()); + fprintf(stderr, "[%s] tile loading failed: %d, %s\n", url.c_str(), res->code, res->error_message.c_str()); #endif } }); diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index c2689162e..2fccf1f40 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -175,21 +176,14 @@ void StyleParser::parseSources(JSVal value) { rapidjson::Value::ConstMemberIterator itr = value.MemberBegin(); for (; itr != value.MemberEnd(); ++itr) { std::string name { itr->name.GetString(), itr->name.GetStringLength() }; - SourceType type = SourceType::Vector; - std::string url; - uint16_t tile_size = 512; - int32_t min_zoom = 0; - int32_t max_zoom = 22; + SourceInfo info; - parseRenderProperty(itr->value, type, "type"); - parseRenderProperty(itr->value, url, "url"); - if (type == SourceType::Raster) { - parseRenderProperty(itr->value, tile_size, "tileSize"); - } - parseRenderProperty(itr->value, min_zoom, "minZoom"); - parseRenderProperty(itr->value, max_zoom, "maxZoom"); + parseRenderProperty(itr->value, info.type, "type"); + parseRenderProperty(itr->value, info.url, "url"); + parseRenderProperty(itr->value, info.tile_size, "tileSize"); + info.parseTileJSONProperties(itr->value); - sources.emplace(std::move(name), std::make_shared(SourceInfo { type, url, tile_size, min_zoom, max_zoom })); + sources.emplace(std::move(name), std::make_shared(info)); } } else { Log::Warning(Event::ParseStyle, "sources must be an object"); diff --git a/src/style/style_source.cpp b/src/style/style_source.cpp new file mode 100644 index 000000000..f46a6fb09 --- /dev/null +++ b/src/style/style_source.cpp @@ -0,0 +1,77 @@ +#include +#include +#include + +#include + +namespace mbgl { + +void parse(const rapidjson::Value& value, std::vector& target, const char *name) { + if (!value.HasMember(name)) + return; + + const rapidjson::Value& property = value[name]; + if (!property.IsArray()) + return; + + for (rapidjson::SizeType i = 0; i < property.Size(); i++) + if (!property[i].IsString()) + return; + + for (rapidjson::SizeType i = 0; i < property.Size(); i++) + target.emplace_back(std::string(property[i].GetString(), property[i].GetStringLength())); +} + +void parse(const rapidjson::Value& value, std::string& target, const char* name) { + if (!value.HasMember(name)) + return; + + const rapidjson::Value& property = value[name]; + if (!property.IsString()) + return; + + target = { property.GetString(), property.GetStringLength() }; +} + +void parse(const rapidjson::Value& value, uint16_t& target, const char* name) { + if (!value.HasMember(name)) + return; + + const rapidjson::Value& property = value[name]; + if (!property.IsUint()) + return; + + unsigned int uint = property.GetUint(); + if (uint > std::numeric_limits::max()) + return; + + target = uint; +} + +template +void parse(const rapidjson::Value& value, std::array& target, const char* name) { + if (!value.HasMember(name)) + return; + + const rapidjson::Value& property = value[name]; + if (!property.IsArray() || property.Size() != N) + return; + + for (rapidjson::SizeType i = 0; i < property.Size(); i++) + if (!property[i].IsNumber()) + return; + + for (rapidjson::SizeType i = 0; i < property.Size(); i++) + target[i] = property[i].GetDouble(); +} + +void SourceInfo::parseTileJSONProperties(const rapidjson::Value& value) { + parse(value, tiles, "tiles"); + parse(value, min_zoom, "minzoom"); + parse(value, max_zoom, "maxzoom"); + parse(value, attribution, "attribution"); + parse(value, center, "center"); + parse(value, bounds, "bounds"); +} + +}