1 #include <mbgl/util/mapbox.hpp>
2 #include <mbgl/util/constants.hpp>
3 #include <mbgl/util/logging.hpp>
4 #include <mbgl/util/url.hpp>
5 #include <mbgl/util/tileset.hpp>
6
7 #include <stdexcept>
8 #include <vector>
9 #include <iostream>
10 #include <cstring>
11
12 namespace {
13
14 const char* protocol = "mapbox://";
15 const std::size_t protocolLength = 9;
16
17 } // namespace
18
19 namespace mbgl {
20 namespace util {
21 namespace mapbox {
22
isMapboxURL(const std::string & url)23 bool isMapboxURL(const std::string& url) {
24 return url.compare(0, protocolLength, protocol) == 0;
25 }
26
equals(const std::string & str,const URL::Segment & segment,const char * ref)27 static bool equals(const std::string& str, const URL::Segment& segment, const char* ref) {
28 return str.compare(segment.first, segment.second, ref) == 0;
29 }
30
normalizeSourceURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)31 std::string normalizeSourceURL(const std::string& baseURL,
32 const std::string& str,
33 const std::string& accessToken) {
34 if (!isMapboxURL(str)) {
35 return str;
36 }
37 if (accessToken.empty()) {
38 throw std::runtime_error(
39 "You must provide a Mapbox API access token for Mapbox tile sources");
40 }
41
42 const URL url(str);
43 const auto tpl = baseURL + "/v4/{domain}.json?access_token=" + accessToken + "&secure";
44 return transformURL(tpl, str, url);
45 }
46
normalizeStyleURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)47 std::string normalizeStyleURL(const std::string& baseURL,
48 const std::string& str,
49 const std::string& accessToken) {
50 if (!isMapboxURL(str)) {
51 return str;
52 }
53
54 const URL url(str);
55 if (!equals(str, url.domain, "styles")) {
56 Log::Error(Event::ParseStyle, "Invalid style URL");
57 return str;
58 }
59
60 const auto tpl = baseURL + "/styles/v1{path}?access_token=" + accessToken;
61 return transformURL(tpl, str, url);
62 }
63
normalizeSpriteURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)64 std::string normalizeSpriteURL(const std::string& baseURL,
65 const std::string& str,
66 const std::string& accessToken) {
67 if (!isMapboxURL(str)) {
68 return str;
69 }
70
71 const URL url(str);
72 if (!equals(str, url.domain, "sprites")) {
73 Log::Error(Event::ParseStyle, "Invalid sprite URL");
74 return str;
75 }
76
77 const auto tpl =
78 baseURL + "/styles/v1{directory}{filename}/sprite{extension}?access_token=" + accessToken;
79 return transformURL(tpl, str, url);
80 }
81
normalizeGlyphsURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)82 std::string normalizeGlyphsURL(const std::string& baseURL,
83 const std::string& str,
84 const std::string& accessToken) {
85 if (!isMapboxURL(str)) {
86 return str;
87 }
88
89 const URL url(str);
90 if (!equals(str, url.domain, "fonts")) {
91 Log::Error(Event::ParseStyle, "Invalid glyph URL");
92 return str;
93 }
94
95 const auto tpl = baseURL + "/fonts/v1{path}?access_token=" + accessToken;
96 return transformURL(tpl, str, url);
97 }
98
normalizeTileURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)99 std::string normalizeTileURL(const std::string& baseURL,
100 const std::string& str,
101 const std::string& accessToken) {
102 if (!isMapboxURL(str)) {
103 return str;
104 }
105
106 const URL url(str);
107 if (!equals(str, url.domain, "tiles")) {
108 Log::Error(Event::ParseStyle, "Invalid tile URL");
109 return str;
110 }
111
112 const auto tpl = baseURL + "/v4{path}?access_token=" + accessToken;
113 return transformURL(tpl, str, url);
114 }
115
116 std::string
canonicalizeTileURL(const std::string & str,const style::SourceType type,const uint16_t tileSize)117 canonicalizeTileURL(const std::string& str, const style::SourceType type, const uint16_t tileSize) {
118 const char* version = "/v4/";
119 const size_t versionLen = strlen(version);
120
121 const URL url(str);
122 const Path path(str, url.path.first, url.path.second);
123
124 // Make sure that we are dealing with a valid Mapbox tile URL.
125 // Has to be /v4/, with a valid filename + extension
126 if (str.compare(url.path.first, versionLen, version) != 0 || path.filename.second == 0 ||
127 path.extension.second <= 1) {
128 // Not a proper Mapbox tile URL.
129 return str;
130 }
131
132 // Reassemble the canonical URL from the parts we've parsed before.
133 std::string result = "mapbox://tiles/";
134 result.append(str, path.directory.first + versionLen, path.directory.second - versionLen);
135 result.append(str, path.filename.first, path.filename.second);
136 if (type == style::SourceType::Raster || type == style::SourceType::RasterDEM) {
137 result += tileSize == util::tileSize ? "@2x" : "{ratio}";
138 }
139
140 #if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(QT_IMAGE_DECODERS)
141 const bool forceWebP = str.compare(path.extension.first, path.extension.second, ".png") == 0;
142 #else
143 const bool forceWebP = false;
144 #endif // !defined(__ANDROID__) && !defined(__APPLE__) && !defined(QT_IMAGE_DECODERS)
145
146 // Replace PNG with WebP if necessary.
147 if (forceWebP) {
148 result += ".webp";
149 } else {
150 result.append(str, path.extension.first, path.extension.second);
151 }
152
153 // Append the query string, minus the access token parameter.
154 if (url.query.second > 1) {
155 auto idx = url.query.first;
156 bool hasQuery = false;
157 while (idx != std::string::npos) {
158 idx++; // skip & or ?
159 auto ampersandIdx = str.find('&', idx);
160 const char* accessToken = "access_token=";
161 if (str.compare(idx, strlen(accessToken), accessToken) != 0) {
162 result.append(1, hasQuery ? '&' : '?');
163 result.append(str, idx, ampersandIdx != std::string::npos ? ampersandIdx - idx
164 : std::string::npos);
165 hasQuery = true;
166 }
167 idx = ampersandIdx;
168 }
169 }
170
171 return result;
172 }
173
canonicalizeTileset(Tileset & tileset,const std::string & sourceURL,style::SourceType type,uint16_t tileSize)174 void canonicalizeTileset(Tileset& tileset, const std::string& sourceURL, style::SourceType type, uint16_t tileSize) {
175 // TODO: Remove this hack by delivering proper URLs in the TileJSON to begin with.
176 if (isMapboxURL(sourceURL)) {
177 for (auto& url : tileset.tiles) {
178 url = canonicalizeTileURL(url, type, tileSize);
179 }
180 }
181 }
182
183 const uint64_t DEFAULT_OFFLINE_TILE_COUNT_LIMIT = 6000;
184
185 } // end namespace mapbox
186 } // end namespace util
187 } // end namespace mbgl
188